ÍNDICE • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • •
116. ESTRUCTURA RAYCASTHIT 115. ESTRUCTURA RAY 114. CLASE RANDOM 113. ESTRUCTURA QUATERNION 112. CLASE PHYSICS (y II) 111. CLASE PHYSICS (I) 110. ESTRUCTURA MATHF (y III) 109. ESTRUCTURA MATHF (II) 108. ESTRUCTURA MATHF (I) 107. CLASE LIGHTMAPSETTINGS 106. CLASE GIZMOS 105. CLASE EVENT (y III) 104. CLASE EVENT (II) 107. CLASE EVENT (I) 106. CLASE DEBUG 105. CLASE CONTROLLERCOLLIDERHIT 104. CLASE COLLISION 103. ESTRUCTURA BOUNDS 102. CLASE INPUT (y IV) 101. CLASE INPUT (III) 100. CLASE INPUT (II) 99. CLASE INPUT (I) 98. CLASE TEXTURE2D (y II) 97. CLASE TEXTURE2D (I) 96. CLASE TEXTURE 95. CLASE GUILAYOUT (y IV) 94. CLASE GUILAYOUT (III) 93. CLASE GUILAYOUT (II) 92. CLASE GUILAYOUT (I) 91. CLASE GUI (y VIII) 90. CLASE GUI ( VII) 89. CLASE GUI (VI) 88. CLASE GUI (V) 87. CLASE GUI (IV) 86. CLASE GUI (III) 85. CLASE GUI ( II)
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • •
84. CLASE GUI (I) 83. CLASE GUITEXTURE 82. CLASE GUITEXT (y II) 81. CLASE GUITEXT (I) 80. CLASE GUIELEMENT 79. CLASE COMPONENT 78. CLASE PHYSICMATERIAL 77. CLASE SHADER 76. CLASE GAMEOBJECT (y VI) 75. CLASE GAMEOBJECT (V) 74. CLASE GAMEOBJECT (IV) 73. CLASE GAMEOBJECT (III) 72. CLASE GAMEOBJECT (II) 71. CLASE GAMEOBJECT (I) 70. CLASE MESH (y II) 69. CLASE MESH (I) 68. CLASE PARTICLEEMITTER (y III) 67. CLASE PARTICLEEMITTER (II) 66. CLASE PARTICLEEMITTER (I) 65. CLASE RENDERTEXTURE (y III) 64. CLASE RENDERTEXTURE (II) 63. CLASE RENDERTEXTURE (I) 62. CLASE CUBEMAP 61. CLASE MATERIAL (y III) 60. CLASE MATERIAL (II) 59. CLASE MATERIAL (I) 58. CLASE LIGHT (y III) 57. CLASE LIGHT (II) 56. CLASE LIGHT (I) 55. CLASE CAMERA (VI) 54. CLASE CAMERA (V) 53. CLASE CAMERA (IV) 52. CLASE CAMERA (III) 51. CLASE CAMERA (II) 50. CLASE CAMERA (I) 49. CLASE MONOBEHAVIOUR (y IX)
• • • • • • • • • • • • • • • • • • • • • • • • •
48. CLASE MONOBEHAVIOUR (VIII) 47. CLASE MONOBEHAVIOUR (VII) 46. CLASE MONOBEHAVIOUR (VI) 45. CLASE MONOBEHAVIOUR (V) 44. CLASE MONOBEHAVIOUR (IV) 43. CLASE MONOBEHAVIOUR (III) 42. CLASE MONOBEHAVIOUR (II) 41. CLASE MONOBEHAVIOUR (I) 40. CLASE BEHAVIOUR 39. CLASE CHARACTERJOINT 38. CLASE SPRINGJOINT 37. CLASE HINGEJOINT (y II) 36. CLASE HINGEJOINT (I) 35. CLASE JOINT (y II) 34. CLASE JOINT (I) 33. CLASE MESHFILTER 32. CLASE RENDERER (y II) 31. CLASE RENDERER (I) 30. CLASE CHARACTERCONTROLLER (y III) 29. CLASE CHARACTERCONTROLLER (II) 28. CLASE CHARACTERCONTROLLER (I) 27. CLASE MESHCOLLIDER 26. CLASE COLLIDER (II) 25. CLASE COLLIDER (I) 24. CLASE RIGIDBODY (y VIII)
• • • • • • • • • • • • • • • • • • • • • • • •
23. CLASE RIGIDBODY (VII) 22. CLASE RIGIDBODY (VI) 21. CLASE RIGIDBODY (V) 20. CLASE RIGIDBODY (IV) 19. CLASE RIGIDBODY (III) 18. CLASE RIGIDBODY (II) 17. CLASE RIGIDBODY (I) 16. CLASE TRANSFORM (y VII) 15. CLASE TRANSFORM (VI) 14. CLASE TRANSFORM (V) 13. CLASE TRANSFORM (IV) 12. CLASE TRANSFORM (III) 11. CLASE TRANSFORM (II) 10. CLASE TRANSFORM (I) 9. ESTRUCTURA VECTOR3 (y IV) 8. ESTRUCTURA VECTOR3(III) 7. ESTRUCTURA VECTOR3 (II) 6. ESTRUCTURA VECTOR3 (I) 5. CLASE OBJECT (y III) 4. CLASE OBJECT (II) 3. CLASE OBJECT (I) 2. ¿DONDE VAN LOS SCRIPTS? ALGUNAS PECULIARIDADES DE UNITY PRELIMINARES Y ALGUNOS PRESUPUESTOS
JUEGOS CASUALES COMO NEGOCIO
Juegos casuales como un negocio La unidad es especialmente adecuado para su uso como una herramienta de desarrollo de juegos casuales. Este artículo discutirá las formas en que los desarrolladores de la Unidad puede sacar provecho de eso y encontrar el éxito como los desarrolladores de juegos casuales. Autor: Tom Higgins Resumen Juegos casuales es un segmento de rápido crecimiento de la industria de los videojuegos en general, y es un segmento en el que un pequeño equipo de desarrolladores puede ser
competitivos y exitosos.Hay una serie de opciones de publicación y distribución disponibles que permiten a los desarrolladores introducir rápidamente sus juegos a una gran audiencia y en espera de los clientes. En el trabajo con los editores y los portales, como la participación de desarrolladores en los ingresos generados por el juego. Con la Unidad, una carrera como desarrollador de juegos casuales está al alcance, le toca a usted ir a por él! 1. ¿Qué son los "juegos casuales"? El término "juegos casuales" significa diferentes cosas para diferentes personas, como el término es todavía sólo vagamente definido en el mejor. A pesar de ello, el término "juegos casuales" todavía se puede utilizar para describir un segmento bastante grande y en rápido crecimiento de la industria del juego. Juegos casuales tienden a ser aquellos que tienen el juego más simple y atractivo diseño del juego, que también tienden a ofrecer a los usuarios la capacidad de intervenir y jugar un partido en pocos minutos sin necesidad de desarrollo a largo plazo de las cualificaciones o el compromiso de juego. El objetivo de los juegos casuales es apelar a la mayor cantidad de audiencia posible, incluyendo aquellos que han caído normalmente fuera de la tradicional "gamer" perfil (personas ajenas a la adolescente a los cambios demográficos veinte hombres). Al igual que con cualquier definición hay zonas grises en ciertos juegos desdibujar las líneas entre casual y graves, pero espero que la esencia básica de la definición viene a través de: pequeño, divertido y fácil de jugar parece que hay tres puntos en común vale la pena recordar. 2. ¿Por qué debería hacer que los juegos casuales Juegos casuales es un segmento en expansión de la industria de los juegos que ofrecen al desarrollador Unidad individual, o el equipo de desarrollo de la pequeña en Unity, una oportunidad muy competitiva para el éxito. El aumento en las probabilidades de éxito se basan en una serie de factores, pero aquí están algunas de las razones más notables. Los juegos competitivos de Oportunidades Casual tienden a ser más pequeñas en su alcance y más limitados en términos de juego en comparación con la consola o títulos AAA de escritorio, y con eso viene más bajos expectativas del usuario final acerca de la experiencia de juego casual. Estos hechos conducen a tiempo mucho menor y los recursos necesarios para desarrollar un título de primer nivel del juego casual, poniéndolas al alcance de personas que se dedican o pequeños equipos de desarrollo.Los canales de distribución disponibles Hay una gran variedad de editores de sitios web en línea y el portal de juegos que se pueden utilizar para publicar el título del juego casual. La verdad es que la mayoría de los individuos o pequeños equipos de desarrollo no tienen los recursos humanos y experiencia en el mercado y promoción efectiva de su juego, ni tampoco tienen una gran comunidad de usuarios existentes en la lista, los editores disponibles y portales de juegos lo hacen. El segmento de los ingresos potenciales casual de la industria de los videojuegos está experimentando un rápido crecimiento, se ha ido de un lado se presenta para el mercado de las consolas más grandes a un dólar de miles de millones al año la industria por su cuenta. Que el crecimiento se ha traducido en un mercado de juegos que permite a los pequeños equipos de desarrolladores (13 desarrolladores) para desarrollar un juego casual de primera línea en unos pocos meses (3-9 meses), y un juego casual estreno puede ganar 100.000 dólares o más. Así como usted puede ver, el mercado de juegos ofrece pequeños equipos de desarrollo una gran oportunidad para ser competitivos y rentables. A continuación encontrará algunos consejos que ayudarán a los desarrolladores realizar su propio éxito en la industria de juegos casuales. 3. Consejos para crear exitosos juegos casuales Hay cursos de todo e incluso programas universitarios que cubren el tema de diseño del juego y los elementos que ayudan a hacer un "buen partido". Este breve artículo no pretende abarcar un tema tan amplio como un todo, sino que se comparten algunas pautas generales que se han recogido las experiencias de primera mano de los desarrolladores, editores y sitios web de juego portal. Piense en su público objetivo Piense en su juego y su público objetivo al escoger una vía de distribución. Navegador gratuito basado en juegos en red llamado a los hombres en adolescentes y veinteañeros que tienden a
preferir la acción, la aventura o los juegos de conducción de estilo, mientras que la apelación descargar ejecutables para un público de más edad y las mujeres cada vez que se tiende a preferir juegos de tipo basados en la historia o un rompecabezas. Que sea sencillo Asegúrese de que el juego es relativamente fácil de aprender y entender, sin embargo, todavía difícil de dominar en general. Las instrucciones deben ser casi ignorable y mantiene al mínimo, un par de frases a lo sumo. Que sea familiar La interfaz de usuario y controles deben ser lo más estándar posible. No imponer una curva de aprendizaje de aquellos que quieren jugar su juego, como que reduce las posibilidades de que disfrutan de la experiencia. Recompensa de un reproductor Mantener a los jugadores interesados y comprometidos con la experiencia de juego. Por ejemplo, en un juego de acción que podría ofrecer una nueva arma, el encendido o enemigo cada 30 segundos o menos para los primeros minutos, y luego cada pocos minutos durante los próximos cinco a diez minutos, y así sucesivamente. O en un juego basado en la historia ofrecen elementos nuevos o modificaciones de carácter en escalas de tiempo similar a la que el jugador invertido en la historia y el juego. Considere la posibilidad de Replayability Diseña tu juego, así que es divertido de jugar en varias ocasiones, tratar de mantener a los usuarios vuelvan a por más. Usted puede hacer esto al ofrecer alternativas caminos de juego, diferentes armas / elemento de selección para los jugadores de repetir o incluso las opciones de caracteres de diálogo que se traducen en juego una experiencia única cada vez que pasa. Llegar a una amplia audiencia Diseño para el mayor de un público lo más posible, ya que aumenta el número de jugadores potenciales. Esto se puede hacer de muchas maneras, pero definitivamente se puede evitar reduciendo el tamaño de su audiencia y aumentar el portal / editor de apelación, evitando el uso de, sangre y vísceras humanas en humanos la violencia y la blasfemia. Concentre sus esfuerzos Maximizar su esfuerzo en el trabajo y evitar pasar demasiado tiempo y recursos en cosas que el usuario es probable que pase la segunda vez. Por ejemplo, no pasar demasiado tiempo en un cómic o la apertura de una escena de corte, en lugar de centrarse en el núcleo del juego el juego en sí. Del mismo modo, evitar pasar demasiado tiempo en el valor de producción de arte, que el tiempo puede ser mejor gastado centrado en el desarrollo de juego único e interesante. Prueba de detección precoz y la frecuencia Han probado el juego por parte de extraños que entran en su base de usuarios objetivo, y han hecho que en varios intervalos durante el proceso de desarrollo. Usted debe tener a prueba el juego en todas las plataformas y configuraciones de destino (sistemas operativos, navegadores y / o publicar formatos, por ejemplo). Es muy importante para conseguir a personas ajenas probar el juego, espontánea y sin recibir instrucciones, mientras mira por encima del hombro para ver lo que les gusta y qué no lo hacen, sólo hablar con ellos después de la prueba el juego se ha completado. Tenga en cuenta que lo anterior no son ciertamente las estrictas normas que deben cumplir, sino que son pautas generales que pueden ser utilizados para ayudar a dirigir su propio esfuerzo. Aprender de los esfuerzos previos de los demás y utilizar esa información a medida que labrarse su propio camino como desarrollador de juegos casuales. 4. Cómo ganar dinero con juegos casuales Hay un gran número de formas en que los desarrolladores pueden hacer dinero con sus títulos de juegos casuales, esta discusión se centrará en el punto de entrada más fácil para los juegos comerciales y que está trabajando con los editores de sitios web existentes y juego portal. La razón de esto es porque muchos individuos o pequeños equipos de desarrollo no tienen tiempo, recursos y experiencia para comercializar con eficacia sus propios juegos a un público de gran escala y la mayoría carecen de una gran base de clientes existentes listo para probar su último juego. Tanto los editores y sitios web de juego portal ofrece los elementos críticos, a su vez, los desarrolladores llevar sus juegos de alta calidad a la mesa y juntos pueden crear y publicar juegos que generan ingresos para que el desarrollador.
Basado en navegador Web Juegos Libre de los juegos basados en navegador web generar ingresos basados en la publicidad se muestra junto con el juego. A medida que el desarrollador de contenidos que usted recibe un porcentaje de los ingresos publicitarios totales generados por el juego. Generalmente, los desarrolladores reciben el 20-40% de los ingresos por publicidad, pero el porcentaje exacto compartida con el desarrollador puede variar por diversas razones. Las principales razones que el porcentaje puede variar son el portal de elegidos, los tipos de publicidad se muestra y si el juego es exclusivo de la página web del portal. Juegos descargables Ejecutables descargables suelen ofrecer el juego libre juego de prueba y luego tiene que ser comprado para desbloquear el juego completo. A medida que el desarrollador de contenido puede recibir un porcentaje de los ingresos por publicidad y / o un porcentaje de los ingresos por ventas generados por el juego. Generalmente, los desarrolladores reciben el 20-40% de los ingresos por publicidad y / o 15-30% de los ingresos por ventas, pero una vez más los porcentajes exactos compartida puede variar.Las principales razones de los porcentajes pueden variar se basan en la publicidad frente a compartir los ingresos por ventas y si el juego es exclusivo de este portal web o editor. Premium Juego Además, los dos juegos basados en navegador y descarga pueden obtener ingresos adicionales si participar en cualquier oferta de portal de característica premium. Algunos sitios web desbloquear el juego adicional para los suscriptores de pago sitio web, mientras que otros ofrecen a los usuarios finales desafío diversos sistemas de clasificación u otras características de construcción de la comunidad. Participar en ese tipo de programas a menudo ofrece participación en los ingresos adicionales y / o el aumento del número de juego. Fijo-Media Publishing Juegos publicados como ejecutables descargables o por tiempo determinado (CD / DVD) a través de las editoriales puede o no puede ofrecer el juego libre juego de prueba y tienen que ser adquiridos para desbloquear el juego completo. A medida que el desarrollador de contenidos puede obtener ingresos de varias maneras, ya sea a través de participación en los ingresos de ventas o de la financiación del desarrollo editorial. Los ingresos compartidos con el desarrollador puede variar aquí más que en los casos anteriores, que dependerá de la fuente de ingresos (revenue share frente a la financiación), su juego en particular y su experiencia con el editor. Así como usted puede ver, el mercado de juegos ofrece pequeños equipos de desarrollo una gran oportunidad para ser competitivos y rentables. A continuación encontrará algunos consejos que ayudarán a los desarrolladores realizar su propio éxito en la industria de juegos casuales. 5. ¿Cuánto dinero puede usted hacer? He aquí dos ejemplos anónimos, los dos son juegos casuales que se han desarrollado y puesto en libertad poco más de un año atrás. En ambos casos, los juegos fueron desarrollados por equipos pequeños en cuestión de meses. Ejemplo # 1 Un juego casual que ofrece tanto como un juego basado en navegador y un juego descargable a través de múltiples sitios web de juego portal, incluyendo shockwave.com y realarcade.com entre otros. El juego logró un éxito moderado, pero que nunca hizo las listas de top-10. Fue capaz de generar aproximadamente $ 40,000 USD para los desarrolladores durante el primer año de su lanzamiento. Ejemplo # 2 Un juego casual se ofrece como un juego basado en navegador a través de dos sitios web de juego portal, shockwave.com y miniclip.com. El juego fue muy exitosa y mantiene constante entre los 10 rankings en los primeros seis meses de su lanzamiento y los primeros lugares-25 hasta el final de su primer año. Fue capaz de generar levemente más de $ 100.000 USD para los desarrolladores en el primer año de su lanzamiento. Hay sin duda los juegos casuales que han hecho menos de los valores citados anteriormente así como hay algunos que han hecho un poco más. El punto aquí no es ofrecer una expectativa de renta fija para todos los desarrolladores tanto como para proporcionar un sentido de escala para el potencial de ingresos que ofrece para quienes participan en los juegos casuales de hoy.
6. Más información Siempre es importante estar informado y participar en la comunidad de desarrolladores a su alrededor.Hay un número de editores y sitios web de juego portal listos para trabajar con los desarrolladores de la Unidad y / o aceptar la Unidad-autor de las presentaciones de hoy. Aquí hay algunos enlaces que ofrecen información sobre la presentación de juegos para algunos de ellos en particular. Adictivo Juegos y Shockwave.com http://www.addictinggames.com http://www.shockwave.com http://www.atomentertainment.com/asw_working_devinfo_games.ht m Big Fish Games http://www.bigfishgames.com/ http://www.bigfishgames.com/company/game-developer.html Freeverse http://www.freeverse.com/ http://www.freeverse.com/about/ Juego Cámara y Real Arcade http://www.gamehouse.com http://www.realarcade.com http://gamedevs.realarcade.com/GameSubmission/index.jsp También hay muchos foros y sitios web donde se puede aprender más sobre la industria de los videojuegos en general, o sobre los juegos casuales en específico. Casual Games Association http://www.casualgamesassociation.org Gamasutra / CMP partido del grupo http://www.gamasutra.com/ International Game Developer Association (IGDA) http://www.igda.org IGDA Juegos Casual Grupo de Interés Especial http://www.igda.org/wiki/index.php/Casual_Games_SIG
FISICAS: CONCEPTOS BASICOS
Examinadas las clases principales de Unity, quisiera ahora realizar una serie de incursiones más o menos extensivas en algunos aspectos del engine que creo que merecen una atención especial, bien por su importancia, bien por la dificultad para entenderlos. Así que, avisando de nuevo en que este que os está
escribiendo no es en absoluto un experto en la materia, intentaré ayudar en lo que buenamente pueda. Quisiera empezar con un acercamiento a todo el componente de físicas del engine, que considero que es el centro neurálgico del noventa y pico por ciento de los videojuegos que se han creado. Existe entre los recién llegados a Unity bastante confusión entre los términos y conceptos que envuelven su apartado de físicas, y en consecuencia a veces no se aplican correctamente los elementos necesarios para –por ejemplo- detectar adecuadamente una colisión o permitir que un determinado gameobject sea inmune a la gravedad. Para que nuestro juego funcione adecuadamente y no consuma más recursos de los necesarios debemos tener muy claro cuál es la mejor solución para cada necesidad. Vamos a intentar desgranar para qué sirve cada cosa. Empezaremos por examinar las diferentes posibilidades de detección de colisiones que nos brinda Unity, y para ello necesitaremos introducir el primer concepto: COLLIDER: Un collider, en términos muy básicos, es un envoltorio que hace que un determinado objeto se torne sólido y en consecuencia pueda chocar con otros objetos (siempre que a su vez esos otros objetos tengan otro collider). Un collider se compone, por un lado, de una determinada forma (que no tiene por qué coincidir con la forma del objeto, aunque es preferible que “casen” de alguna manera, para originar colisiones creíbles) y, por otro, de un determinado material físico (aquí no estamos hablando de colores o modos de reflejar la luz de una superficie, sino de capacidad de rebote y/o fricción de dicho objeto, así que no confundamos el término “material” que usamos para renderizar un objeto con el material físico). Atendiendo a su forma, podemos diferenciar los colliders en dos subgrupos: colliders de malla (mesh colliders) y colliders de primitivas (primitive colliders): El mesh collider lo creamos cuando importamos una malla desde alguna aplicación 3d tipo Blender o 3dMax. Al seleccionar dicha malla desde la vista de Proyecto y previo a importarlo, debemos (caso de querer este tipo de malla) marcar la casilla “generate colliders” (ver captura de pantalla) para que nuestra malla tenga un collider de este tipo.
Cuando marcamos esta casilla e importamos la malla, Unity genera un collider que tendrá la misma forma que dicha malla (de ahí el nombre). Podríamos pensar: “pues ya está, es tan sencillo como pedir a Unity que genere colliders para todas las mallas que vayamos importando y ya tenemos nuestro sistema de detección de colisiones montado, sin necesidad de complicarnos la vida asignando colliders de primitivas que además no cuadran tan perfectamente con nuestras mallas como los colliders de malla”. Pero no es tan sencillo. Para empezar, dos colliders de malla no colisionarán entre sí si al menos uno de ellos no tiene marcado en el inspector el checkbox “convex”, que hace que el collider envuelva adecuadamente la malla. “No hay problema” –podemos pensar- “marco la casilla convex y asunto solucionado”. Sigue sin ser tan sencillo. Si dos colliders de malla colisionan entre sí a una velocidad importante es posible que no se detecte la colisión. “¿y para el caso de objetos que sé positivamente que no se moverán a mucha velocidad puedo usar
mesh colliders?” La respuesta es: poder, se puede, pero si se puede evitar, mejor. Pensemos que la cantidad de recursos que consume un PC para calcular colisiones derivadas de mallas complejas es muy superior a la precisa para hacer lo propio con primitives colliders. En resumen, que deberíamos usar los colliders de malla en objetos que sepamos positivamente que en nuestro juego se moverán poco o nada, que no estarán sometidos a colisiones continuas con otros objetos y que tengan una forma difícil de casar con un collider primitivo.
Por su parte, el primitive collider implica asignar a una malla un collider prefabricado por Unity, pudiendo meramente escoger entre las siguientes formas primitivas: esfera, caja, cápsula o rueda. Se trataría meramente de –teniendo nuestra malla seleccionada- irnos al menú Components=>Physics y seleccionar uno de los indicados colliders, para después moverlo de tal forma que encaje lo mejor posible con nuestra malla. En ocasiones precisaremos usar varios colliders primitivos para cubrir toda la fisonomía de una malla. Esto se puede conseguir creando un gameobject vacío y emparentar dichos colliders como hijos de esta, para crear así una unidad manipulable, por decirlo de alguna forma. Aunque este segundo sistema –primitive collider- es más trabajoso y antiestético que el anterior- mesh collider- es recomendable que en la medida de lo posible lo usemos para nuestras mallas.
FISICAS: CONCEPTOS BASICOS (II)
Veíamos en la entrada anterior que un collider vendría a ser meramente un caparazón que le añadimos a una malla para que pueda colisionar con otras mallas, pudiendo ese caparazón tener la misma forma que la malla (mesh collider) o una forma que nosotros le diseñemos para él a partir de una o varias formas primitivas (primitive collider)que Unity nos ofrece. Supongamos entonces que tenemos dos mallas en nuestra escena, a cada una de las cuales le asignamos un collider (del tipo que sea, eso no importa para este ejemplo). A través de un script vinculado a una de las mallas movemos su transform para que colisione contra la otra malla. Probadlo si queréis con dos cubos (que ya traen "de serie" un collider primitivo (obviamente, un box collider)). Comprobaréis que pese a tener cada malla un collider, a efectos prácticos es como si no lo tuvieran, ya que se atraviesan. Esto sucede porque para que dos mallas puedan colisionar, además de estar ambas dotadas de su pertinente collider, es preciso que al menos una de las dos mallas tenga vinculado además un Rigidbody no kinemático. Olvidémonos de momento del palabro "kinemático" y centrémonos en el Rigidbody. Un rigidbody es el conjunto de datos que permiten a Unity calcular, entre otras cosas, las consecuencias que tendrá una colisión para el gameobject al cual se asigna dicho rigidbody. Al igual que nos pasaría a nosotros si estuviéramos en clase de física, para que Unity calcule las
consecuencias de una hipotética colisión necesita tener en cuenta factores como la masa del gameobject al que se vincula, la velocidad relativa a que se produce la colisión, si existe o no rozamiento y en qué medida, si hay o no gravedad, etc. Por resumir, asignando un rigidbody a un gameobject automáticamente estamos dotando a dicho gameobject de una serie de propiedades físicas, tales como gravedad, rozamiento y peso, ya que sin saber la masa de un objeto - por ejemplo- es imposible calcular el resultado de una colisión (probad si no a patear de manera indiscriminada objetos con diferentes masas, paredes de carga y yorkshires inclusive, para que comprobéis las diferentes posibilidades de desplazamiento). Lo más impactante para los recién llegados a Unity cuando le asignamos un rigidbody a una malla -como muchos ya habréis comprobado- es que automáticamente, salvo que el suelo a su vez tenga otro collider, nuestra malla se pierde en las profundidades de la escena. Por eso se tiende por algunos (manuales de pago incluidos) a simplificar en ocasiones cuando se explica lo que es un rigidbody, limitándolo a "lo que hace que un objeto se vea afectado por la gravedad". Y por ahí suelen empezar también las confusiones entre colliders y rigidbodies, debido a que los dos tienden a solaparse y es difícil explicar uno sin mencionar al otro. Es verdad que ambos son necesarios para que se produzca una colisión, pero intervienen en apartados distintos: el collider sería el dónde (se produce la colisión) y el rigidbody el cómo. Supongo que se entiende que no tendría sentido asignar un rigidbody a una malla que no tenga un collider, ya que de nada sirve calcular una colisión que nunca tendrá lugar (ya que no hay collider para esa malla con el que colisionar o ser colisionado) Sí cabe pensar en un collider que no tenga rigidbody. De hecho, es lo que en Unity se conoce como "static colliders". Un collider estático, como su nombre indica, está pensado para objetos que no se van a mover en la escena. Este tipo de colliders sin rigidbody pueden interactuar/colisionar con otros colliders siempre que éstos colliders sí tengan vinculado un rigidbody (ya hemos visto que si ninguno de los dos objetos tiene un rigidbody la colisión no se produce, básicamente porque Unity no tiene ninguna forma de calcular las consecuencias de dicha colisión entre dos objetos "no físicos" y directamente obvia la colisión). Lo que sucede es que cuando un collider dinámico (con rigidbody) se mueve y fruto de ese movimiento colisiona con un collider estático (sin rigidbody), como Unity ignora las propiedades físicas del collider estático, meramente éste -haciendo honor a su nombre- no se moverá. Y de hecho, si se moviera, y precisamente por lo expuesto (Unity no sabe calcular cómo y cuánto se ha de mover) el costo computacional sería brutal. Por todo lo anterior, los static colliders son óptimos para las partes fijas de nuestro juego, tal como paredes, librerías, árboles y objetos similares que no han de moverse pero que tampoco han de atravesarse por los personajes y demás partes móviles del juego. Espero que estos conceptos que estoy intentando explicar a cuentagotas queden claros. Sé que al principio parece todo un poco confuso, pero es esencial entenderlos correctamente.
0. PRELIMINARES Y ALGUNOS PRESUPUESTOS
Antes de entrar en materia y para que nadie se lleve a engaño, considero honesto advertir a los hipotéticos lectores de que el autor de estas líneas no es programador profesional, ni ha creado ninguno de los cien videojuegos más vendidos de la historia. De hecho, si he de ser totalmente sincero, hace menos de tres años este que os está escribiendo no había escrito ni una sola línea de código. Puede parecer por lo tanto un atrevimiento -y de hecho lo es- que un tipo como yo, un autodidacta sin ninguna base académica, se atreva a inaugurar un blog de estas carácteristicas, y comprenderé perfectamente que alguien con conocimientos profundos sobre programación en general o Unity en particular salga corriendo de este site. Reconozco ya de antemano, por lo expuesto, que muy probablemente algunas de las cosas que os vaya explicando en las lecciones que vendrán a continuación puedan contener errores, y pido disculpas anticipadas por ello. Huelga decir que agradeceré cualquier corrección por parte de personas con más conocimientos. Me gustaría explicaros brevemente las razones por las cuales me he decidido a crear este blog, las razones por las que creo que este blog es necesario: Yo aterricé en el universo Unity hace unos cuatro meses. Más o menos como todos, me di una vuelta por la interfaz gráfica, estuve peleándome con las cámaras, encendí las luces y puse niebla a la escena, trasteé con el terrain hasta crear algo parecido a una isla, importé algunos modelos de Blender (todavía ando buscando las texturas de esos modelos) y cosas parecidas. Luego empecé a leerme todos los libros que pillé sobre Unity, que no es que sean demasiados. Y cuando me los hube leído todos, ya dominaba más o menos decentemente la interfaz del engine, pero seguía sin tener ni idea sobre la API de Unity. En los libros que en la actualidad hay sobre la materia la parte relativa a los scripts -que en el fondo es la más importante- se deja en un segundo término. Se incluyen, un poco a boleo, varios scripts, pero de una manera totalmente desordenada y sin ningún interés pedagógico, pareciera que más para impresionar al respetable o para copiar y pegar en otros proyectos sin saber a ciencia cierta qué se está haciendo, que para realmente explicar de dónde sale cada cosa, cuáles son las clases principales, su cometido y relación de herencia entre ellas, qué demonios en un cuaternión, etc. La única vía para hacerse con la API de Unity pasaba pues por ir al manual de referencia de la propia página web del engine, la cual presenta un triple inconveniente: 1)Está en inglés. 2)Está escrito más como manual de consulta que como instrumento de aprendizaje. 3)Sigue un orden alfabético, no un orden lógico. El primer inconveniente no lo fue tanto, ya que hace cuatro años (uno antes de empezar a aprender informática) seguí al pie de la letra el consejo de un amigo: - Si quieres aprender a programar, antes aprende inglés.
Y como este que os habla suple su falta de talento con una buena dosis de cabezonería, a estudiar inglés que me puse. De esta manera y gracias al consejo de mi amigo -que ruego os apliquéis los que no os llevéis bien con la lengua de Shakespeare- durante estas últimas semanas he traducido la casi totalidad de las clases, estructuras y enumeraciones de Unity al castellano. No es que sea una traducción muy pulida, aviso desde ya, pero para nuestros fines bastará. De los otros dos inconvenientes nos tendremos que ir ocupando poco a poco, por el viejo procedimiento de prueba y error y usando el sentido común y la constancia donde el resto falle. Tengo el firme propósito de ir haciendo vuestros los avances que a su ver yo vaya haciendo en mi propio aprendizaje, y aunque ello entrañe ir a un ritmo lento y en ocasiones nos implique tener que retroceder para revisitar algún concepto, también creo que puede ser interesante para alquien que empieza en esto escuchar las explicaciones y/u opiniones de otro que recién está aprendiendo también y que -al contrario que algunos autores con más conocimientos- no da nada por supuesto. Así que no esperéis scripts de cuatro páginas (no al menos el primer año), con todo lo bueno y lo malo que eso pueda conllevar. Y, por supuesto, sería muy positivo que hubiera la suficiente retroalimentación con todo aquél que esté interesado en hacer común el camino de aprendizaje. Eso sí, para no tener que hacer una parada conjunta antes de dar siquiera el primer paso, esto es, para poder seguir este "curso" (por llamarlo de alguna manera) presupongo que quien lea esto tiene una serie de conocimientos de base ya adquiridos, a saber: 1) Algo de programación en general, y de Javascript en particular. (Variables, funciones, bucles, condicionales, etc.) 2) Conocimientos rudimentarios de POO (Programación Orientada a Objetos) 3) Haberle dado unas cuantas vueltas a la interfaz de Unity y estar familiarizado con su terminología (gameobjects, assets, y esas cosas) Si no tenéis esos conocimientos previos, ni podréis seguir las explicaciones ni podréis hacer juego alguno (desde ya les aseguro a los amigos de los atajos que no existe un software mágico que obedeciendo a tus impulsos cerebrales te construya de la nada un juego online multijugador listo para ser distribuido). Y sin más preámbulos, creo que podemos dar la introducción por acabada y meternos en materia.
1. ALGUNAS PECULIARIDADES DE UNITY
Antes de adentrarnos en la API de Unity, conviene saber que el engine admite tres lenguajes de programación para sus scripts: C#, Boo (una variante de Python) y algo muy parecido a Javascript, que es lo que vamos a usar nosotros en este curso. Javascript es de los tres lenguajes indicados el más sencillo, y si bien algunos programadores con un cierto nivel aseguran que C# les ofrece más control, para nuestros fines y nivel con Javascript vamos más que sobrados. Una de las cosas que choca de entrada en Unity para los que tuvieran un conocimiento previo de Javascript es la forma de declarar las variables. A diferencia del Javascript standard, en Unity hemos de declarar el tipo de la variable, un poco a la antigua usanza de C y C++. Por ejemplo, para declarar la variable numero de tipo int hariamos:
var numero : int; numero = 10; Y si la quisiéramos inicializar en la declaración, sería: var numero : int = 10;
Esto es, palabra reservada "var" para indicar que vamos a declarar una nueva variable, el nombre que le demos a ésta seguido de dos puntos y el tipo de la variable declarada.
En otro orden de cosas, hemos de decir que la API de Unity está orientada a objetos y se compone de algo más de un centenar de clases, de las cuales una tercera parte más o menos están relacionadas entre sí en términos de herencia y el resto o son conjuntos de funciones auxiliares (clase tiempo, de funciones matematicas, etc) o bien meramente permiten funcionalidades especiales no contempladas en lo que podríamos llamar las clases troncales (clases relativas a la GUI o para conectarse a un servidor, por ejemplo) Entendiendo que las clases que están vinculadas entre sí son a su vez las que nos pueden dar una idea más precisa del mecanismo de funcionamiento de Unity, son con vuestro permiso -y con alguna excepción que iremos haciendo- por las que me gustaría empezar. Y ya que -salvo error- nadie se ha tomado la molestia de hacer un esquema del sistema de herencia de estas clases, me tomé la libertad de hacerlo yo. Está incompleto, ya que una veintena de clases de las que estuve mirando cuando estaba llevando a cabo la traducción no me parecieron demasiado importantes, o por lo menos no a estas alturas, pero en esencia este sería el arbolito de clases con que nos va a tocar lidiar durante una buena temporada.
Para agrandar la imagen pulsa AQUÍ Recomiendo imprimir este esquema, ya que lo vamos a usar de referencia para, por ejemplo, saber qué funciones hereda una determinada clase, o qué clase tiene un atributo que es una instancia de otra clase. Por experiencia -poca, pero experiencia al cabo- os digo que o tenéis una referencia a mano o todo os acabará sonando igual y al final os quedaréis sin el que debiera ser vuestro objetivo, que no es aprenderse todas las funciones de memoria, sino entender por qué la API de Unity está estructurada de una manera y no de otra. Como podréis observar, he incluido en el esquema las variables, funciones y funciones de clase (o atributos y métodos, si preferís la terminología de la POO) de la clase base (Object), la principal de las que derivan de aquélla (Component) y la que a su vez hereda de ésta y es heredada por otro número de clases. Esto nos permite, como decía antes, seguir la pista a las variables y funciones heredadas por -pongamos un ejemplo- la clase Camera, que hereda de Behaviour y por tanto también de Component y por último de Object. Así, podemos presuponer que esto es válido:
var camaraDeSeguridad : Camera; camaraDeSeguridad.name = "Mi camara"; Esto es, nuestro objeto camera debería haber heredado una variable name de la clase Object. Teniendo a mano el esquema nos podemos evitar andar rebuscando por el manual de referencia.
2. ¿DONDE VAN LOS SCRIPTS?
Bueno, tras un par de capítulos con los prolegómenos, creo que ya podemos entrar en materia, para lo cual vamos a abrir Unity. Nota: En principio, y salvo que diga lo contrario, todas las características y scripts que vamos a utilizar sirven para la versión free como para la PRO, así que me da igual la versión que tengáis. Vamos a crear un proyecto nuevo, que podéis llamar como os dé la gana y guardar donde queráis, que para eso e ordenador es vuestro. No vamos a importar de momento ningún asset, así que dejamos desmarcadas todas las casillas y creamos el proyecto. Lo único que nos aparecerá en la interfaz, por tanto, será la cámara principal. La marcamos y en el inspector, la colocamos en las siguientes coordenadas: X=0, Y=1, Z =-5. Ahora vamos a introducir un cubo en la escena. Nos vamos al menú superior, Gameobject => Create other => Cube. Al cubo primero lo vamos a castellanizar, y en consecuencia le llamaremos Cubo. Le podemos cambiar el nombre en el inspector, o bien pulsando F2 en la jerarquía con Cube seleccionado. Vamos a ubicar nuestro cubo en las coordenadas 0,0,0. Vamos a salvar la escena. File =>Save Scene. Para no liarnos, recomiendo que le pongáis el mismo nombre que yo, esto es, Ejemplo_1. Si todo ha ido bien, debéis tener algo parecido a esto:
Ahora, en la vista Project, en un espacio libre debajo de donde está la escena guardada, hacemos click derecho con el ratón, y en el menú emergente seleccionamos Create => Folder. A la carpeta que aparecerá le llamaremos Mis scripts. Con el puntero del ratón sobre dicha carpeta vamos a hacer de nuevo click derecho, y esta vez seleccionamos Create=>Javascript. Nos aparecerá un icono representando al nuevo script con el nombre por defecto "NewBehaviourScript". Lo renombramos, llamándolo MiPrimerScript, y le damos a return para que nos conserve el cambio.
Como podemos observar, en el inspector al seleccionar nuestro script aparece una función por defecto, la cual nos será útil en un buen número de casos. Sería lo que se conoce como una función sobreescribible, que quiere decir que Unity decide cuándo se va a llamar (la función Update, que es la que sale por defecto, es llamada por Unity cada frame) y nosotros decidimos qué hará cuando sea llamada, introduciendo código en su interior. Para poder editar y/o crear nuestros scripts, hemos de acceder al editor que viene con Unity, y la mejor manera para hacerlo es hacer doble click sobre el nombre de nuestro script. Se nos abrirá (en windows) esto:
Vamos a aprovechar que tenemos el editor abierto para explicar otra característica de los scripts en Unity. Unity permite asignar/modificar los valores a las variables globales desde la propia interfaz de usuario, y concretamente desde el inspector, bien asignando esos valores "a mano", o bien arrastrando un objeto o componente del tipo de dicha variable global. Para que podamos hacer eso es preciso, como digo, que la variable tenga un ámbito global, y para ello es preciso que la misma se declare fuera de cualquier función. Lo veremos mejor con un ejemplo: en el editor de scripts, encima de donde pone function Update, escribiremos lo siguiente:
var camaraDeSeguridad : Camera; camaraDeSeguridad.name = "Mi camara"; Debug.Log(camaraDeSeguridad.name); Es un miniscript parecido al que veíamos en el capítulo anterior. En él declaramos una variable global (está fuera de cualquier función) de tipo "Camera", y lo que hacemos a continuación es -vía herenciaasignarle un nombre a la variable, que será "Mi camara". La tercera declaración de momento nos basta
con saber que muestra en pantalla (imprime) el valor de lo que esté entre sus paréntesis. Guardamos el script (si no, no funcionará) en el editor de scipts. De vuelta a la interfaz de Unity, si seleccionamos el nombre del script vemos que en el inspector se ha actualizado dicho script con el código que hemos introducido. No obstante, nuestro script aún no es funcional, ya que no lo hemos vinculado a ningún objeto de nuestra escena. Pensemos que los scripts por sí sólos no hacen nada, de la misma forma que cualquier asset que está en la carpeta/vista del proyecto (por ejemplo, una textura) no participará de alguna manera en una escena de nuestro juego si no lo arrastramos a esa escena, convirtiéndolo en parte de cualquier gameobject. En consecuencia, vamos a vincular nuestro script a uno de los game objects de nuestra escena, y en concreto al cubo. Para hacer eso meramente hemos de arrastrar el script desde la vista de proyecto hasta el propio objeto cubo, bien sea sobre su nombre en la jerarquía, bien sobre la propia imagen del cubo en la escena. Tras arrastrar el script, y con el cubo seleccionado en la jerarquia, en el inspector deberíamos estar viendo lo siguiente:
Obsérvese que en el inspector aparece ahora el nombre de nuestro script como una parte más del Cubo que tenemos en la escena. Y daros cuenta de que la variable global queda expuesta en el inspector, de tal forma que no necesitamos ir al editor de scripts para asignarle un valor. Si la variable hubiera sido, por ejemplo, de tipo float, podríamos haber introducido o cambiado su valor meramente escbibiéndolo en el inspector. Si en cambio la variable fuera de tipo bool, nos aparecería en el inspector con un checkbox al lado del nombre, bien marcado (true) o sin marcar (false) para que lo cambiáramos a conveniencia. Pero como en este caso la variable es de tipo Camera, lo único que podemos hacer para inicializarla es proveerla de un objeto de ese tipo. Dado que en nuestra escena precisamente tenemos una cámara (la main camera), meramente tendremos que arrastrarla desde la jerarquía hasta el lugar del inspector donde se consigna el valor de la variable,y que ahora mismo pone "none". Tras arrastrar la cámara, el inspector lucirá así:
Ya tenemos, por lo tanto, nuestro script asignado a un gameobject (nuestro cubo) y la única variable global inicializada con otro object (la cámara principal). Procedamos ahora a darle al play y ver qué sucede. Si nos fijamos, debajo de la ventana game nos aparecerá impreso el nombre que le asignamos a nuestra cámara.
Antes de acabar me gustaría que practicáramos un pequeño ejercicio que nos permitirá comprender las diferentes maneras (correctas e incorrectas) en que se puede declarar una variable. Borremos todo el contenido de nuestro script MiPrimerScript y tecleemos lo siguiente:
var sinTipoNiIni; var conTipoSinIni : int; var sinTipoConIni = 10; var conTipoConIni : int = 10; var aPlazos : int; aPlazos = 10; private var miSecreto : int; var arrastrame : GameObject;
function Update() { var enLaFuncion : int; }
Salvamos el script y, con el Cubo seleccionado (recordemos que este script está todavía vinculado al cubo que tenemos en la escena) echamos un vistazo al inspector. Podemos de manera empírica llegar a las siguientes conclusiones para cada manera de declarar una variable: La variable sinTipoNiIni, que como su nombre apunta hemos declarado sin indicar ni el tipo de datos que debería contener, ni la hemos inicializado conjuntamente con la declaración, no aparece en el inspector, porque éste no tiene forma de saber qué tipo de variable estamos declarando. La variable conTipoSinIni sí es tenida en cuenta por el inspector, ya que aunque no la hemos inicializado, sí que hemos indicado el tipo de dato que queremos almacenar en ella. La variable sinTipoConIni sí aparece en el inspector, ya que aunque no hemos declarado el tipo que contendrá, al estar inicializada permitimos a Unity deducir en base al valor en tipo de variable apropiado. Fijémonos que además de aparecer la variable en el inspector, lo hace con el valor inicial que le hemos dado. La variable conTipoConIni aparece obviamente en el inspector. La variable aPlazos aparece en el inspector, pero sin inicializar. En cambio, si pulsamos play observaremos que automáticamente le es asignado el valor 10 que le dimos en segunda declaración. La variable miSecreto no es accesible desde el inspector porque es privada. De hecho, esa variable no podrá se accedida desde ningún script distinto del que la contiene. La variable arrastrame es recogida en el inspector, y al ser de un tipo propio de Unity, nos aparecerá con una flechita diminuta en la derecha que nos indica que podemos importar del juego cualquier componente de ese tipo para asignar un valor a la misma, bien usando el menú que emerge al clickar dicha flecha, bien vía arrastrar el objeto o componente desde las carpetas de proyecto o jerarquía. Por último, la variable enLaFunción no aparece en el inspector, ya que al ser declarada dentro de una función no tiene carácter público.
Bueno, pues con esto ya debería quedar un poco más clara la manera en que las variables y los scripts son tratados en Unity. Para la próxima lección empezaremos a analizar clase por clase de la API.
3. CLASE OBJECT (I)
Ha llegado el momento de empezar a pelearnos con las clases que componen la API de Unity, y tal como os comentaba considero lo más conveniente empezar con la clase base. Es sabido que en POO las clases base de las que heredan las demás suelen tener una funcionalidad bastante limitada, cuando no inexistente, siendo su función más la de punto de partida de las que habrán de heredarla que la de instanciar objetos de dicha clase. La clase Object, que es con la que vamos a empezar, no parece ser una excepción a esta regla.
La clase Object consta de dos variables, dos funciones y nueve funciones de clase (FC). Como sabréis, las funciones de clase se diferencian de las funciones standard en que aquéllas no precisan ser llamadas por una instancia u objeto de la clase, perteneciendo -como su nombre indica- a la clase y no a las instancias de dicha clase.
VARIABLES: name: var name : String Bueno, esta variable ya la conocíamos. Hace referencia al nombre del objeto, y comparten ese nombre tanto el objeto en sí como los componentes de dicho objeto. Esta variable nos sirve tanto para cambiar el nombre a nuestro objeto...
name = "Cualquier nombre"; ...como para obtener el nombre de un objeto. Probemos esta segunda opción con un ejemplo muy sencillo. Abrimos Unity y nos vamos a la escena que creamos en la lección anterior. En la vista del proyecto, hagamos doble click en el script que llamamos miPrimerScript. Una vez se abre el editor, borramos todo el contenido y:
function Update(){ Debug.Log(name); } Como vimos en la lección anterior, Debug.Log nos permite imprimir en pantalla el valor de lo que se halle entre paréntesis. Salvamos el script (pero no cerramos el editor, meramente lo minimizamos) Hay una cuestión que no quiero dejar pasar por alto. Lo que hemos modificado y guardado es el script que se halla en la carpeta de proyecto. No obstante, al hacer eso automáticamente se modifican todas las copias de este script que tengamos por ahí. En el caso que nos ocupa, recordaremos que a nuestro cubo le habíamos añadido una copia de este script, que a estas alturas por lo expuesto ya habrá sido modificado. Probémoslo. Si le damos al play, observaremos que bajo la ventana game aparece el nombre de nuestro cubo. Todo bien hasta aquí. no obstante, en la definición del manual de referencia dice que todos los componentes del objeto comparten el mismo nombre. Si echamos un vistazo a nuestro cubo en el inspector, observaremos que entre otros componentes tiene uno llamado transform, así que vamos a
hacer la prueba. Sustituimos el "name" que habíamos escrito en el editor de scripts entre paréntesis por transform.name. O sea:
Debug.Log(transform.name); Guardamos el script, le damos al play y....Voilá!, el componente transform comparte el nombre "cubo".
hideFlags: var hideFlags : HideFlags Esta variable no parece a priori tener una gran utilidad. Se conseguiría con su manipulación hacer que un determinado objeto desaparezca o bien del inspector, o bien de la jerarquía, o impedir que dicho objeto se pueda editar, o no permitir que un objeto sea salvado con la escena y en consecuencia destruído con la nueva escena que se cargue. Para tal fin esta variable manera las diferentes opciones de una de las muchas enumeraciones que hay en Unity. Esta enumeración, de nombre HideFlags, tiene las siguientes opciones: HideInHierarchy____El objeto no aparecerá en la jerarquía. HideInInspector____El objeto no aparecerá en el inspector. DontSave_____El objeto no será salvado en la escena ni destruido en la nueva que se cargue. NotEditable______El objeto no será editable en el inspector. HideAndDontSave___Combinación de HideInHierarchy y DontSave.
Así, si por ejemplo quisiéramos (no se me ocurre muy bien para qué) que el cubo desapareciera del inspector mientras se desarrolla el juego, le vincularíamos un script parecido a este:
var meEvaporo: GameObject;meEvaporo.hideFlags = HideFlags.HideInInspector; Este script lo podríamos arrastrar a la cámara principal, y con posterioridad arrastrar nuestro cubo en la variable global meEvaporo, que está esperando un gameObject. Observaremos que cuando le demos al play, el cubo desaparece del inspector. ¿Por qué meEvaporo es de tipo GameObject?¿No debería tratarse de un Object, que es la clase que estamos tratando?. Pues efectivamente, pero sucede que si sustituimos GameObject por Object en nuestro script, comprobaremos que desaparece del inspector la variable meEvaporo, posiblemente por lo que decía al principio de la lección de que la clase Object está pensasa como clase base para dotar de funcionalidad a las que la heredan, que para ser instanciada ella misma. En cualquier caso, como digo, no parece que hideFlags sea una variable que vayamos a usar mucho.
4. CLASE OBJECT (II)
FUNCIONES: ToString: function ToString () : String Devuelve un string con el nombre del gameobject que hace la consulta. GetInstanceID: function GetInstanceID () : int Unity asigna a cada objeto un identificador único. Esta función devuelve ese identificador. Vamos a utilizar estas dos funciones en un mismo ejemplo. En el capítulo anterior teníamos vinculado nuestro script-para-todo a la cámara principal. Abrimos el editor de scripts, borramos todo y tecleamos lo siguiente:
var todoSobreMi: GameObject; Debug.Log("El nombre de este objeto es " + todoSobreMi.ToString() + " y su id unica es " + todoSobreMi.GetInstanceID()); El script no merece mucha explicación. Asegurémonos de arrastrar el cubo a la variable todoSobreMi en el inspector. Al pulsar el play deberia mostrarse el nombre e id del cubo, tal como se muestra en la imagen (obviamente, la id no tiene por qué coincidir con la que os aparezca a vosotros)
FUNCIONES DE CLASE
operator bool, == y != Estas tres funciones méramente habilitan la posibilidad de establecer comparaciones de
igualdad/desigualdad entre dos objetos o componentes, o (en el caso del operador bool)si existe dicho objeto componente y tiene un valor distinto de null. Por ejemplo:
if (rigidbody) Debug.Log("Este gameobject tiene vinculado un Rigidbody"); Que sería lo mismo que haber escrito
if (rigidbody != null) Debug.Log("Este gameobject tiene vinculado un Rigidbody");
Instantiate: static function Instantiate (original : Object, position : Vector3, rotation : Quaternion) : Object Esta función lo que hace es clonar el objeto que le pasamos como primer parámetro, y devolver ese clon del objeto original, ubicándolo en posición y rotación determinadas. Observamos que el parámetro "position" es de tipo Vector3, que es una clase que no tardaremos mucho en estudiar. De hecho, posiblemente sea la próxima que tratemos, ya que aunque no pertenece a la jerarquía de herencia que hemos tomado como referencia para el orden de estudio, sí que va a aparecer el número suficiente de veces durante el tránsito por dicha jerarquía como para no ocuparnos de ella de primeras. Un Vector3 es, en pocas palabras, un punto en el espacio tridimensional. Dicho punto viene dado por las coordenadas en el eje X (derecha-izquierda) Y (arriba-abajo) y Z (delante-detrás). Cada unidad se corresponde a una unidad en Unity (valga la redundancia), que a su vez equivale a un metro. Por lo tanto para que un objeto se desplace un metro a la derecha, escribiríamos en nuestro Vector3 (1,0,0). Notemos que esto es ni más ni menos que lo que hace de manera más visual las tres variables position del transform que aparecen en el inspector. El otro parámetro de la función, rotation, es de tipo Quaternion. Quien sepa lo que es, felicidades, yo estuve varios días peleándome con apuntes de matemáticas por la red y a lo sumo me hice una idea abstracta de que un cuaternión se compone de tres números reales y uno imaginario, el cálculo de los cuales establece la rotación de un objeto. Si os sirve de consuelo, y eso lo indica el propio manual de referencia oficial de Unity, es muy raro que trabajemos directamente con cuaterniones, sino que lo haremos con funciones que nos simplifiquen el trabajo. Yo personalmente me quedo solamente con la idea de que un cuaternión lo componen cuatro elementos y mide las rotaciones. Con eso es suficiente, creedme. Bueno, pues dicho esto a ver si somos capaces de instanciar/clonar un cubo. Nos vamos a Unity, y le quitamos a la cámara principal el script que le habíamos colocado anteriormente. Para hacer eso, con la cámara seleccionada en el inspector colocamos el ratón sobre el nombre del script, botón derecho=>Remove Component. Ahora, para no andar vinculando scripts a gameobjects que nada tienen que ver con él (como hicimos no hace mucho con la cámara), vamos a crear un gameobject vacío cuya única utilidad sea contener nuestro script. Para ello nos vamos al menú superior, y le damos a GameObject=>Create empty. A la carpeta que nos aparecerá en la jerarquía la renombramos (F2) como PortaScripts. Vale, ahora doble clic sobre nuestro script en la vista de Proyecto para abrir el editor de scipts, y escribimos:
var reproducete : GameObject; Instantiate(reproducete, Vector3(2.0,0,0), Quaternion.identity); Aquí lo que hemos hecho meramente es dejar una variable global "expuesta" (que pueda ser accesible
desde el inspector) y llamar a la función instantiate, de tal manera que clonará el Gameobject que le arrastremos y lo situará dos unidades/metros a la derecha del objeto original. Quaternion.identity meramente significa que el objeto clonado tendrá rotación cero, esto es, su transform rotation estará a 0,0,0 (salvo que el objeto clonado dependa a su vez de otro objeto, en cuyo caso tendrá la misma rotación que el objeto padre, pero esto ya lo explicaremos cuando toque. Salvamos el script y lo arrastramos hasta nuestro PortaScripts en la jerarquía. Vemos que en el inspector, con PortaScripts seleccionado, aparece el script y la variable expuesta que llamamos reproducete. Arrastramos el cubo hasta ella y ya podemos darle al play. Debería aparecernos un segundo cubo, tal que así:
Podemos observar alguna cosa más. En la jerarquía aparece -mientras se está reproduciendo la escenaun segundo cubo, el cual Unity nos indica expresamente que es un clon del original. Si lo seleccionamos, podemos ver en su transform en el inspector que la variable x (el eje derecha/izquierda) no está en el cero, sino en el dos, tal como le habíamos indicado. Probemos otra cosa. Detenemos la reproducción de la escena. Seleccionamos el cubo original, y le damos a los ejes X e Y de transform rotation los valores 25 y 65 respectivamente.El cubo girará sobre dichos ejes. Démosle al play. Podemos observar que el cubo clonado se muestra alineado con la cuadrícula global, y no con el cubo original. Esto es lo que conseguimos con Quaternion.identity. La función instantiate es muy usada para crear proyectiles, partículas en explosiones e incluso AI (inteligencia artificial) para enemigos. Cabe una segunda forma distinta para la función Instantiate, que es esta: static function Instantiate (original : Object) : Object Como podemos observar, aquí meramente estamos duplicando el objeto, se tal suerte que el clonado se ubicará en el mismo lugar y con la misma rotación que el original. Podemos probarlo en nuestro script meramente eliminando el segundo y tercer parámetro. Al darle al play, sabemos que ha aparecido un clon porque así lo indica la jerarquía, pero no podemos distinguirlo porque está justo en el mismo sitio que el original y se están solapando.
Destroy: static function Destroy (obj : Object, t : float = 0.0F) : void Como su nombre indica, esta función borra del juego un gameobject, un componente o un asset. Tiene dos parámetros, siendo el primero el elemento a borrar y el segundo el tiempo en segundos que tardará
en borrarlo desde que se llame a la función (si no se indica lo contrario, por defecto el parámetro indica cero segundos, esto es, la destrucción del objeto es automática). Hay una cuestión que igual ahora no se entiende muy bien, pero la dejo apuntada para tratarla más en profundidad en otro momento: si en el parámetro obt colocamos un Componente, la función sólo eliminará ese componente, haciéndolo desaparecer del Gameobject al que pertenezca. Pero si en cambio lo que le pasamos a la función es un gameobject, se destruirá tanto el gameobject como todos los hijos de ese gameobject (esto es, todos los objetos y componentes e inclusive otros gameobjects que dependan del eliminado en una relación de parentesco). Para tener un acercamiento intuitivo a esta relación de dependencia, pensemos en un gameobject coche que tiene, entre otros, cuatro componentes rueda y un gameobject conductor que hemos vinculado al gameobject coche para que allá donde se desplace el coche vaya el conductor. Si un misil destruye un componente rueda usando la función destroy, sólo se destruirá la rueda. Si en cambio lo que destruye es el gameobject coche, se destruirá tanto el vehículo como las ruedas como el conductor. Vamos a probar la función Destroy. Para empezar devolvamos a nuestro cubo original a su rotación original (0,0,0). Vamos a rehacer ahora nuestro sufrido script, que tenemos vinculado al cubo. En el editor de scripts modificamos nuestro código así:
var reproducete : GameObject; var nasioPaMorir : GameObject; nasioPaMorir = Instantiate(reproducete, Vector3(2.0,0,0), Quaternion.identity); Destroy (nasioPaMorir, 10.0); Creo que es bastante fácil de entender. Lo que hemos hecho es añadir una segunda variable de tipo GameObject, que no vamos a inicializar desde el inspector, porque lo que hará será almacenar el cubo clonado que devuelve la función Instantiate. Inmediatamente es llamada la función Destroy, que borrará el elemento clonado que hemos almacenado en nasioPaMorir pasados diez segundos. Salvamos, le damos al play, contamos diez, y adiós clon.
DestroyImmediate: static function DestroyImmediate (obj : Object, allowDestroyingAssets : boolean = false) : void Esta función destruye inmediatamente un objeto, igual que la función anterior. Desde el manual de Unity se nos dice, no obstante, que es preferible usar Destroy en lugar de esta función, ya que puede borrar más de lo que deseamos. Por lo tanto, olvidémonos de DestroyImmediate.
5. CLASE OBJECT (y III)
Continuamos con las funciones de clase de la clase Object. Recordemos que las funciones de clase, como su nombre indica, no van vinculadas a objetos o instancias de una clase, sino a la clase en sí, esto es, no se utiliza el operador punto entre el nombre del objeto/instancia y el de la función, como sucedería en una función normal (o método) dentro de una clase.
FindObjectOfType static function FindObjectOfType (type : Type) : Object
Esta función devuelve el primer objeto activo que Unity encuentre que sea del tipo que le pasamos como parámetro. Veámoslo en un ejemplo. Recuperamos una vez más nuestro script, borramos todo y tipeamos lo siguiente:
var dameAlgo : Object; dameAlgo = FindObjectOfType(Camera); Debug.Log("Deberia haber encontrado 1 " + dameAlgo); Salvamos, le damos al play y observamos que Unity ha encontrado nuestra cámara principal. Nos advierte el manual de referencia que esta función puede ser un poco lenta y costosa en términos de rendimiento, por lo que se recomienda no usarla a su vez como parte de una función que se actualice cada frame (como por ejemplo la función Update, que estudiaremos a no mucho tardar)
FindObjectsOfType static function FindObjectsOfType (type : Type) : Object[]
No hace falta ser muy perspicaz para darse cuenta de que esta función es idéntica a la anterior, con la diferencia de que aquí lo que se devuelve no es el primer objeto activo de tipo Type, sino que se devuelven en un array todos los objetos activos cuyo tipo coincide con el que solicitamos. Veamos la diferencia rehaciendo nuestro script:
var dameAlgo : Object[]; var chivato : String; dameAlgo = FindObjectsOfType(GameObject); chivato = "He encontrado " + dameAlgo.Length + " objetos: "; for(var contador = 0; contador < dameAlgo.Length; contador++) chivato += dameAlgo[contador].name + " "; Debug.Log(chivato); Lo que hemos hecho aquí es lo siguiente: primero hemos reconvertido nuestra variable dameAlgo en un array de Objects. Declaramos después una variable de tipo string para que vaya recopilando toda la información que al final imprimiremos. Inicializamos luego nuestro array con todos los objetos de tipo GameObject que Unity encuentre en nuestra escena. A partir de ahí,montamos un bucle for para ir añadiendo a nuestro string "chivato" el nombre de todos los objetos encontrados. Finalmente, imprimimos. Si todo ha salido bien, el resultado tendría que ser este al darle al play: DontDestroyOnLoad static function DontDestroyOnLoad (target : Object) : void Con esta función conseguimos que el objeto que colocamos como parámetro no se destruya cuando se cargue una nueva escena. Por defecto, cuando el jugador cambia de escena o nivel, todos los objetos de la escena que abandona se destruyen antes de crear los que corresponden al nuevo nivel. Con esta función, conseguimos que los objetos que queramos pervivan al cambio de escena.
Dado que aún no sabemos cargar escenas nuevas, no vamos a realizar ningún ejemplo. Ya lo haremos cuando expliquemos las oporunas funciones.
Y con esto acabamos el análisis de nuestra primera clase. Espero que os hayan quedado los conceptos más o menos claros (al principio cuesta un poquillo, así que no desesperéis). Y por supuesto, si hay alguna duda, queja (educada) o corrección que hacer, sentiros libres de comentar.
6. ESTRUCTURA VECTOR3 (I)
Lo primero que cabría decir de Vector3 es que, a diferencia de Object, por ejemplo, no es en sí una clase, sino una estructura. En la corta historia de la programación las estructuras surgieron antes de las clases, en los viejos tiempos de la programación procedimental, y originalmente eran poco más que una colección de variables que guardaban algún tipo de relación entre sí. Hoy por hoy, no obstante, no hay ninguna diferencia entre una estructura y una clase. Aunque Vector3 no está contenido dentro del esquema de herencia principal de Unity, es una clase imprescindible para la creación de cualquier juego, ya que precisamente es la que permite que representemos puntos y vectores en el espacio 3D, y por ende la que nos permite trabajar con distancias
y desplazamientos. El concepto de punto, siendo sencillo cuando se entiende, a veces provoca en los recién llegados no pocas confusiones. Quizás lo mejor para captar el concepto de punto en 3D previamente tengamos que aprehender el concepto de punto en 2D. Vamos a intentar explicarlo de una manera llana, y ruego me disculpen los que ya tenían claro el concepto: Delante vuestro posiblemente tengáis la pantalla de vuestro ordenador o del dispositivo electrónico desde el que estáis viendo estas líneas. En algún lugar de la pantalla estará seguramente el cursor del ratón, apuntando a algún punto (valga la redundancia) determinado de ésta. Si quisiéramos explicarle a alguien que no está viendo la pantalla en qué lugar está el cursor, o si meramente quisiéramos dejar por escrito de una manera fiable en qué punto exacto está ahora nuestro cursor, una buena manera de hacerlo sería decir: el cursor está X centímetros (digamos por ejemplo 16) a la derecha del borde izquierdo de la pantalla e Y centímetros por debajo del borde superior de la pantalla (pongamos 20). Así, si por ejemplo convenimos que la esquina superior izquierda de la pantalla es el punto 0,0, nuestro cursor en el mundo 2D de la pantalla estará situado en el punto 16,20. Por lo tanto, observamos que para determinar la situación exacta de un punto necesitamos a su vez otro punto que nos sirva de referencia (Y es importante retener este concepto, sobre el que no tardaremos en volver). Así, para saber dónde está el punto al que señala el ratón necesitamos primero compararlo con el punto que tomamos como referencia, y la resta entre ambos puntos es la distancia que los separa, que es este caso ya no sería un punto sino un vector, aunque se represente igual. Lo mismo es aplicable a las tres dimensiones, aunque a nivel intuitivo nos cueste un poco más de pillarlo. Meramente entendamos que si nuestra pantalla de ordenador no fuera plana, sino por ejemplo un holograma, para localizar el objetivo de nuestro ratón tendríamos que añadir un nuevo punto (o eje) Z, para calcular la profundidad con relación a un punto de origen (que podría ser la esquina superior izquierda más cercana a nosotros de la pantalla holográfica en 3d) La estructura Vector3, pues, nos permite averiguar en qué punto se halla cada gameobject, cambiar su posición, calcular las distancias entre ellos, etc. Es conveniente advertir que el tipo Vector3 no es la única clase/estructura que maneja lo relativo a puntos y desplazamientos. Cumplen funcionalidades parecidas (que no iguales) la clase Quaternion y la Matrix4X4. Existen, antes de que nos alarmemos en exceso, funciones que permiten convertir, por ejemplo, un Vector3 en un Quaternion (y viceversa), así que de momento no nos obsesionemos más de la cuenta con conceptos que nos suenen extraños. Y tras esta extensa introducción, en el próximo capítulo empezamos con la estructura en sí.
7. ESTRUCTURA VECTOR3 (II)
CONSTRUCTOR: 1)static function Vector3 (x : float, y : float, z : float) : Vector3
Como sabemos de nuestros conocimientos de programación orientada a objetos, el constructor es una función especial cuyo cometido es crear de manera adecuada las instancias/objetos de una determinada clase o estructura. La función constructora se caracteriza por tener el mismo nombre que la clase.
Tal como vemos en el prototipo de nuestro constructor, le hemos de pasar al mismo los tres componentes de nuestro punto 3D, referidos al eje horizontal, vertical y de profundidad respectivamente, en formato de punto flotante. Así:
var miPunto = Vector3(2.0, 5.0, 4.8);
referenciamos un punto que con respecto al origen se desplazará dos unidades a la derecha, cinco hacia arriba y 4,8 hacia el fondo. Vale, pero ¿cuál es el origen?, se preguntará alguien. Pues depende, tendremos que responder. Si un objeto no depende de otro, entendemos por origen el punto 0,0,0 de las coordenadas globales (que es donde deberíamos tener nuestro cubo) y desde ese punto origen se calcula el desplazamiento, pero si un objeto depende de otro (recordemos el ejemplo de la rueda y el coche) caben para ese objeto dos orígenes o coordenadas diferentes: las globales (distancia de la rueda respecto del eje 0,0,0 del caso anterior) y las locales (referidas al objeto del que depende)
2)static function Vector3 (x : float, y : float) : Vector3 Como vemos, hay una segunda función constructora que no toma en cuenta el eje Z (la profundidad) asumiendo que ésta es cero. Por tanto, si a la función constructora Vector3 sólo le pasamos dos parámetros, automáticamente será llamada esta segunda versión del constructor.
VARIABLES:
x, y, z:
Estas variables se refieren a los componentes X, Y y Z del vector, respectivamente.
this: var this[index : int] : float
Esta es otra forma para acceder a los componentes X, Y y Z del vector, siendo los respectivos índices de cada componente 0, 1 y 2. Así, estas dos declaraciones son equivalentes:
var miVector : Vector3; miVector.x = 3.0; miVector[0] = 3.0; //Equivale a la anterior
magnitude: var magnitude : float Esta variable devuelve la longitud o tamaño de un vector. Habíamos hablado previamente de que un vector3 contiene el número de unidades que lo separan del origen dado en los ejes X, Y y Z. Obviamente, cuanta mayor es la distancia entre nuestro Vector3 y el punto de origen, mayor la longitud del vector. La longitud del vector equivale a la raiz cuadrada de (x*x+y*y+z*z). Soy consciente de que a estas alturas la utilidad de funciones como estas es difícil de apreciar. Con un poco de paciencia, y cuando las combinemos con otras clases, veremos el partido que se le puede sacar
a todo esto.
sqrMagnitude: var sqrMagnitude : float
Si de lo que se trata es de comparar dos vectores, es preferible usar esta función que la precedente, ya que aunque parezca antiintuitivo, Unity calcula con más rapidez los cuadrados de las magnitudes que las magnitudes en sí.
normalized: var normalized : Vector3
Esta variable devuelve un vector con la misma dirección que el original, pero con magnitud 1. Es importante constatar que el vector original permanece inalterado y lo que se devuelve es meramente una copia que conserva la dirección de dicho original, aunque con la longitud alterada. Si en lugar de normalizar una copia quisiéramos hacer lo propio con el original, entonces tendríamos que usar la función Normalize, que veremos en breve. Decir también que si el vector es demasiado pequeño para ser normalizado, lo que se devuelve es un vector a cero.
VARIABLES DE CLASE:
zero: static var zero : Vector3
Es meramente un atajo para poner un vector a cero.
transform.position = Vector3.zero; // Es lo mismo que escribir transform.position = Vector3(0,0,0);
one, forward, up y right:
Son respectivamente atajos para escribir Vector3(1,1,1), Vector3(0,0,1), Vector3(0,1,0) y Vector3(1,0,0).
Vamos a ver algunos de los conceptos de esta lección en un ejemplo. Abrimos nuestro script favorito y escribimos:
var unObjeto : GameObject; var desplazamiento : Vector3; var magnitud : int = 0; desplazamiento = Vector3.right; for(var contador = 0; contador < 3; contador++) { unObjeto.transform.position += desplazamiento; magnitud += desplazamiento.magnitude;
Debug.Log("Longitud del Vector del cubo: " + magnitud); }
Salvamos, arrastramos nuestro cubo para que se convierta en la variable unObjeto y le damos al play. Nuestro cubo se mueve tres unidades a la derecha y bajo la ventana Game aparece la magnitud de desplazamiento del vector. El script funciona de la siguiente forma: Además de la variable que sostendrá a nuestro cubo, creamos una variable de tipo Vecto3 para poder almacenar a modo de impulsos maniobras de desplazamiento de una unidad a la derecha. Ese input a la derecha se lo daremos tres veces mediante un bucle for, y cada magnitud individual (que obviamente vale 1 cada vez) se acumula en una variable de tipo int que hemos creado a tal efecto.
8. ESTRUCTURA VECTOR3(III)
Scale: function Scale (scale : Vector3) : void
Multiplica el Vector3 que llama la función por el Vector3 que le pasamos a dicha función como parámetro. Lo veremos mejor con un ejemplo. Vamos a darle un meneo de nuevo a nuestro script. Tecleamos:
var unObjeto : GameObject; var miVector : Vector3; miVector = unObjeto.transform.position = Vector3(2.0, 1.0, 3.0); Debug.Log("Magnitud inicial del cubo " +miVector.magnitude); miVector.Scale(Vector3(3.0, 1.5, 2.0)); unObjeto.transform.position = miVector; Debug.Log("Magnitud final del cubo " +miVector.magnitude);
Antes que nada vamos a explicar lo que pretendemos hacer: dejamos expuesta la variable unObjeto para arrastrar con posterioridad nuestro cubo desde el inspector. Asimismo declaramos una variable para sostener el valor original y modificado para un vector3. Aunque aún no lo hemos estudiado, a nadie sorprenderá que los diferentes gameobjects que ocupan la escena han de ir ineludiblemente vinculados a un objeto transform, el cual a su vez es el que sirve para fijar en qué punto del espacio 3D se halla el gameobject. Así, para obtener o indicar la posición de un gameobject en el espacio usaríamos la sintaxis gameobject.transform.position. Se da la circunstancia que la variable position de la clase transform es de tipo Vector3, por lo que nos sirve para nuestro ejemplo. Hacemos una iniciación doble, de tal manera que damos unos valores a nuestro Vector3 miVector, que a su vez tendrá el mismo valor que le damos a la posición actual del cubo. Obtenemos la magnitud de dicho vector y la imprimimos. Luego usamos la función Scale, y la magnitud del vector modificado es impreso de nuevo, y a la par el cubo se desplaza por segunda vez.
Obtendremos al darle al play un resultado como este
Si nos fijamos en la parte inferior de la ventana Game, sólo aparece impresa la magnitud final del vector de nuestro cubo. Para ver también el valor inicial, hemos de hacer un click sobre dicho texto impreso, de tal suerte que nos aparecerá una ventana emergente con ambos valores, tal como mostramos:
La función Scale tiene un segundo prototipo, que es el que sigue static function Scale (a : Vector3, b : Vector3) : Vector3
El fin último es, al igual que en la función homónima, multiplicar dos vectores. La diferencia es que aquí pasamos expresamente los dos vectores que queremos multiplicar entre sí. También difieren ambas funciones en que la primera no devuelve ningún valor (el cambio resultante de la multiplicación es directamente asignado al objeto que la llama) y esta en cambio sí devuelve un Vector3 con el resultado, que podemos por lo tanto asignar a una variable.
Normalize: function Normalize () : void
Esta función hace que el Vector3 que la llama pase a tener una magnitude de 1, conservando, eso sí, la misma dirección. Como decimos, esta función cambia el vector que la llama; si lo que pretendemos es conservar sin cambios el vector corriente y crear un nuevo vector normalizado, hemos de usar -como ya aprendimos en el capítulo anterior- la variable normalized.
ToString: function ToString () : String
Devuelve nuestro vector convertido en un string y formateado. Para ver como funciona, añadamos al final del script que usamos en el ejemplo anterior la siguiente línea:
ebug.Log("Mi vector como string formateado: " + miVector.ToString());
Veremos que nos aparece el vector resultado de la multiplicación del anterior ejemplo convertido en un string que podemos imprimir. Dejamos para el próximo capítulo las dos funciones de clase que nos restan para acabar la explicación de la estructura Vector3.
9. ESTRUCTURA VECTOR3 (y IV)
FUNCIONES DE CLASE:
Lerp: static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3
Esta función interpola linealmente entre dos vectores, desde from hacia to en el valor t. Vamos a intentar explicarlo de la manera más clara posible, ya que se trata de una función importante: Lerp realiza una línea imaginaria que va del punto que le pasamos como primer parámetro (from) al punto que le pasamos como segundo parámetro (to). Esa línea imaginaria es entonces una recta con un origen y un final, y entre ambos extremos tiene muchos puntos intermedios. Pensemos por ejemplo en una regla, para tener más o menos claro el concepto. Pongamos que al punto de origen (from) le asignamos el valor 0, y al punto final (to) le damos el valor 1. En ese caso, el punto que está en la mitad de nuestra recta imaginaria es evidente que valdrá 0,5. Con esto claro planteemos ahora el mismo asunto pero al revés. Tenemos el punto origen y el punto final y queremos saber dónde está la mitad exacta entre esos dos puntos. Lo que haríamos es llamar a la función Lerp, asignar a los parámetros primero y segundo respectivamente las coordenadas de origen y final y darle al tercer parámetro (que en el prototipo recibe el nombre de "t", y es de tipo float) el valor 0.5. El valor retornado por la función se corresponderá con el punto situado en el centro de los dos extremos dados. El parámetro t puede recibir valores entre 0.0 (que coincide con el origen) hasta 1.0 (que coincide con el final).
Vamos a practicar. En Unity, le quitamos provisionalmente a nuestro gameobject PortaScripts el script, para que no nos dé errores en lo que a continuación haremos. Acto seguido vamos al menú gameobject=> creater other => capsule. Colocamos la cápsula a un valor -3 en el eje x del transform en el inspector. Renombramos a esta cápsula y la llamamos Origen. Creamos luego otra cápsula, y en la coordenada x de su transform la pondremos en 3. La rebautizamos como Fin (Final es una palabra reservada) Es preferible que tengamos la escena enfocada desde la vista superior (lo que conseguimos haciendo click en el eje Y del gizmo) Ahora hacemos doble click en el script (que aunque hemos borrado del Portascripts sigue estando en el Proyecto), y tecleamos:
var principio : Transform; var fin : Transform; function Update () { transform.position = Vector3.Lerp(principio.position, fin.position, 0.3); }
Salvamos el script y lo arrastramos a nuestro cubo. Acto seguido seleccionamos el cubo y en el inspector nos aparecerán nuestras dos variables. Arrastramos origen a principio y destino a fin. Si todo va bien, nuestro cubo debiera situarse a tres décimas partes de la línea hipotética que separa las cápsulas, contando desde la que hemos dado en llamar origen. ¿Lo probamos?
Bien. Parece que funciona, pero podemos ir un poco más allá. Para ello vamos a recurrir a una clase que aún no hemos estudiado, que es la clase Time, y concretamente a la variable time de la clase Time. Time.time es una variable que representa los segundos transcurridos desde que el juego se inicia. Por lo tanto, la variable vale cero al iniciarse el juego, 1 un segundo después, etc. Con esto en mente, vamos a nuestro último script y dentro de la función Lerp sustituimos el 0.3 por Time.time, salvamos y le damos al play. Efectivamente, nuestro cubo empieza pegado al origen (el parámetro t, representado por Time.time vale
cero) y va desplazándose hacia el fin hasta que t vale 1, o sea, hasta que transcurre un segundo. Sabiendo esto, podemos hacer que el trayecto de nuestro cubo dure por ejemplo diez segundos en lugar de uno. Conseguimos esto meramente multiplicando Time.time * 0.1. Podemos, dándole una vuelta más de tuerca al asunto, crear una variable expuesta (susceptible de ser accesada desde el inspector) de tipo float, que el usuario pueda modificar, y que al ser multiplicada por Time.time haga que nuestro cubo viaje más rápido o más lento.
Slerp: static function Slerp (from : Vector3, to : Vector3, t : float) : Vector3
Esta función es similar a la anterior, con la particularidad de que en lugar de interpolar en línea recta lo hace en forma esférica. Para probarla no tienes más que sustituir el término "Lerp" del script anterior por "Slerp", y poner el cubo en órbita. Si tienes tiempo y ganas, ubica los gameobjects Origen y Destino en otras posiciones a través de sus respectivos transforms en el inspector (desplázalos también en sus ejes Y y Z).
10. CLASE TRANSFORM (I)
Vamos a dedicar los próximos capítulos a estudiar una de las clases más importantes de la API de Unity. La clase Transform, como vemos en el gráfico, hereda de Component, que a su vez deriva de Object, y por lo tanto esta clase tiene como propias las variables y funciones de las dos clases de las que hereda. La pregunta parece obligada: si Transform hereda de Component, ¿por qué no estudiamos la clase Component primero? No lo hacemos porque la clase Component, en el fondo, es poco más que una recopilación del resto de clases. Lo veremos mejor si recuperamos el gráfico general de clases que vimos en su día:
Si nos fijamos con atención, observaremos que la mayoría de las variables de la Clase component son a su vez instancias (objetos) de un buen número de las otras clases, algunas de las cuales incluso -como la propia clase Transform- derivan de ésta. Nos veríamos pues, caso de estudiar primero la clase Component, obligados a explicar lo que es un transform, un rigidbody, una camera, etc, antes de explicar las clases que los desarrollan. Por lo tanto, para no liarnos en exceso, dejaremos la clase Component para bastante más adelante. Bien. ¿Qué es un transform?. Posiblemente muchos de los que ya hayan trasteado con la interface de Unity asimilarán esta palabra a la sección homónima del inspector. Efectivamente, si seleccionamos cualquier gameobject de la escena, automáticamente el inspector nos muestra en la sección transform los valores de posición, rotación y escala de dicho gameobject. No puede existir un gameobject en la escena sin su propio transform. Si nos fijamos, hasta nuestro PortaScripts tiene vinculado un transform, pese a que como mero contenedor de scripts no es renderizado en la escena, y por lo tanto es invisible. En consecuencia, todo gameobject en nuestra escena está situado en un lugar determinado de ésta (position), con una determinada inclinación (rotation) y le es aplicada una determinada escala comparativa de tamaño (scale). Con esto en mente, empecemos.
VARIABLES:
position: var position : Vector3
Esta variable nos indica en qué punto del espacio global se ubica nuestro transform (y por ende el gameobject al que el mismo va vinculado). Recordemos que el espacio global es el que hace referencia a las coordenadas de la escena, y no a las de un determinado objeto. Si quisiéramos consultar/indicar el lugar del espacio local de un objeto, utilizaríamos la variable localPosition. La variable position es de tipo Vector3, y nos permite tanto obtener los datos de posición del transform como modificarlos para darle una nueva ubicación. Por ejemplo, si quisiéramos ubicar un objeto en el centro del espacio global, haríamos:
elObjetoQueSea.transform.position = Vector3(0, 0, 0);
Podemos asimismo acceder a uno sólo de los ejes de la posición, tal como sigue:
Debug.Log(elObjetoQueSea.transform.position.x);
Lo cual nos permitiría imprimir la posición que tiene nuestro transform referida al eje horizontal.
localPosition: var localPosition : Vector3
Como adelantábamos hace un momento, localPosition indica la posición del transform al que dicha variable pertenece, calculada en términos relativos al transform del que aquél depende. Parece más difícil de lo que es en realidad, tal como vamos a demostrar con un ejemplo sencillo: Nos vamos a Unity. Si hemos guardado nuestra última sesión, debemos tener en nuestra escena, aparte de nuestro viejo amigo el cubo, dos cápsulas de nombre origen y destino. Eliminamos Destino. Si por lo que sea no hubiéramos conservado las cápsulas, creamos un gameobject capsule nueva. Esa cápsula (u Origen, si guardamos la última escena)la ubicamos desde el inspector en la posición (3, 4, 2) Resumiendo, tenemos en la escena(o debiéramos tener) el cubo en las coordenadas de posición (0,0,0) y una cápsula en la posición (3,4,2). Ninguno de estos gameobjects es hijo a su vez de otro, por lo que están y se mueven en coordenadas globales. Abrimos nuestro script y escribimos:
transform.position = Vector3(-2,-1,-1);
Salvamos y arrastramos el script a nuestro cubo. Al darle al play, vemos que éste se desplaza respecto la posición que ocupaba en las coordenadas globales (el centro justo) dos unidades a la izquierda, una hacia abajo y una hacia delante. Al darle al pause, nuestro cubo vuelve a su lugar original, en 0,0,0 (es importante retener esto) Vamos ahora a, en la Jerarquía, arrastrar el gameobject cubo y soltarlo dentro de gameobject cápsula (u Origen). Vemos que el cubo mantiene su posición en la vista de escena, pero si lo seleccionamos (está ahora contenido en la cápsula), sus coordenadas en el transform del inspector son ahora las inversas que las que le dimos al gameobject cápsula. Esto es así porque ahora para el cubo sus coordenadas de posición no dependen ya del espacio global, sino del local de la cápsula de la cual depende. Para el cubo la coordenada 0,0,0 no es ya el centro de la escena, sino el centro del gameobject capsula, y dado que con respecto a la cápsula el cubo está desplazado tres unidades a la izquierda, cuatro hacia abajo y dos hacia delante, son las que muestra. El hecho de que el cubo dependa de la cápsula o dicho en terminología Unity, el cubo sea hijo de la cápsula, no implica que no pueda seguir siendo localizado/ubicado en coordenadas globales. De hecho, si le damos al play de nuevo, habida cuenta de que estamos utilizando en nuestro script la variable position (global), el cubo se seguirá desplazando al mismo sitio al que lo hacía cuando aún no dependía de la cápsula. Pero, obviamente, al tener ahora un padre, le podemos aplicar ahora al cubo la variable localPosition, y así podemos modificar el script como sigue:
transform.localPosition = Vector3(-2,-1,-1);
Vemos ahora que el movimiento del cubo lo es en relación con la posición que ocupa papá capsula (o para ser más precisos el transform de papá cápsula). Probad si no lo veis claro a pasarle un vector (0,0,0)
a nuestro script en lugar del que hemos escrito antes. Si le aplicáramos esta variable localPosition a un transform sin padre, dicho transform se movería en el espacio global, tal como si le hubiéramos aplicado position y no localPosition.
11. CLASE TRANSFORM (II)
eulerAngles: var eulerAngles : Vector3
Esta variable indica la rotación del transform en grados. Se corresponde plenamente con los valores que aparecen en el apartado rotation de cada transform en el inspector. Los valores de esta variable vienen dados, al igual que los de position, en formato Vector3. Lo que sucede es que un valor de (90,0,0) referido a la posición implica que nuestro transform se desplace 90 unidades a la derecha, mientras que los mismos valores relativos a la rotación implicarían que nuestro transform gire 90 grados (el equivalente a un cuarto de vuelta) sobre el eje X. Al igual que con la variable position, eulerAngles nos permite consultar individualmente la rotación de nuestro transform sobre un determinado eje. Pero si lo que queremos no es consultar sino cambiar la rotación, deberemos indicar los tres valores vía un Vector3, y Unity los convertirá entonces en los valores que él usa internamente. Porque -esto no sé si lo he dicho antes- Unity trabaja internamente con Quaterniones, por motivos de rendimiento y exactitud, mientras que a los humanos nos resulta más sencillo hacerlo con grados Euler, así que en la práctica esisten unos "atributos de traducción" que nos permite pasar de quaterniones a grados y viceversa, a fin de que hombres y máquina sigan trabajando con los elementos que mejor se adapten a sus capacidades. Al igual que con position, existe una variable eulerAngles para funcionar con valores globales (la que estamos estudiando) y otra localEulerAngles que trabaja con valores locales (dependientes del padre del transform que rota) A esta variable sólo podemos asignarle valores exactos, no de incremento, ya que fallaría pasados los 360º. Para incrementar una rotación de esta forma, tenemos que usar la función Transform.rotate. Usemos un ejemplo muy simple. Antes arrastremos nuestro cubo en la jerarquía fuera de la cápsula, para desemparentarlo. Y rehacemos por enésima vez nuestro script (que debería continua vinculado al cubo)
transform.eulerAngles = Vector3(60,0,0);
Podéis ver que nuestro cubo ha girado un 60 grados sobre el eje X, entendido éste como un alambre invisible que cruza de manera horizontal la escena (por decirlo de alguna manera) Como colofón, añadir que Unity automáticamente convierte los angulos Euler que le pasamos al transform en los valores usados para la rotación almacenada en Transform.rotation.
localEulerAngles: var localEulerAngles : Vector3
Aquí no deberíamos necesitar grandes explicaciones: localEulerAngles se refiere a la rotación en grados de un transform con relación a la que tiene el transform del cual depende en una relación hijo/padre. Al igual que con la variable anterior, Unity automáticamente convierte los angulos Euler en los valores usados para la rotación almacenada en Transform.localRotation.
rotation: var rotation : Quaternion
Es la variable que contiene la rotación del transform en el mundo global almacenada en quaterniones. Como ya hemos dicho, Unity almacena internamente todas las rotaciones en quaterniones, lo cual no quiere decir que nosotros tengamos que trabajar directamente con ellos. Por norma general, o bien trabajaremos con grados Euler que luego se convertirán implícita o explicitamente en quaterniones, o bien a lo sumo usaremos alguna función que nos permita encapsular las operaciones con quaterniones.
localRotation:
var localRotation : Quaternion
Obviamente, esta variable indica la rotación en quaterniones de un transform relativa a la rotación de su padre
right, up y forward: var right : Vector3 var up : Vector3 var forward : Vector3
Estas variables hacen referencia respectivamente al eje X(derecha/izquierda), Y (arriba/abajo) y Z(delante/detrás) de las coordenadas globales del transform. No las hemos de confundir con las variables del mismo nombre de la estructura Vector3. Así, Vector3.up es un atajo que equivale a Vector3(0,1,0), esto es, implica un movimiento de una unidad hacia arriba. En cambio, transform.up meramente hace referencia al eje arriba/abajo, pero no implica movimiento. Para mover un transform por esta vía tendríamos que hacer algo como esto: En Unity eliminamos el script que tenemos vinculado a nuestro cubo. En Proyecto abrimos nuestro script y tecleamos:
var unObjeto : GameObject; unObjeto.transform.position = transform.right*10; unObjeto.transform.eulerAngles = transform.up*45;
Arrastramos nuestro script tras salvarlo a PortaScripts en la Jerarquía y con Portascripts seleccionado arrastramos el cubo a la variable expuesta. Play. Efectivamente, el cubo se desplaza 10 unidades a la derecha y 45 grados sobre el eje Y.
12. CLASE TRANSFORM (III)
Vamos a explicar las variables que nos quedan en esta lección.
localScale: var localScale : Vector3
Como su nombre indica, es una variable de tipo Vector3 que permite consultar o establecer la escala de un transform en relación con la de su padre. Obviamente, un transform con esta variable en 0,0,0 tendrá la misma escala que su padre.
parent: var parent : Transform
Hace referencia al padre del transform (que es obviamente otro transform), y a través de esta variable se puede consultar y/o cambiar dicho padre. Veamos primero de qué forma podemos consultar valores relativos al padre de nuestro transform: Nos vamos a Unity, arrastramos en la Jerarquía el cubo dentro de la cápsula, y a PortaScripts le borramos el script para que no nos dé errores. En Proyecto le damos doble click a miPrimerScript y escribimos lo que sigue:
Debug.Log(transform.parent.gameObject.name);
Arrastramos tras salvar el script dentro del cubo (que está dentro de la cápsula). Play. Aparece el nombre de la cápsula,que es lo que pretendíamos. Fijaros cómo a través del operador punto le decimos al Unity (leyendo de derecha a izquierda) que busque el nombre del gameobject al que pertenece el transform que es el padre del transform del gameobject que hace la consulta (al que va vinculado el script) y lo imprima en pantalla.
Vamos ahora a darle un padre nuevo a nuestro cubo. Abrimos el script y, sin borrarla, desplazamos la declaración que habíamos escrito un par de renglones hacia abajo, y encima escribimos lo siguiente:
var nuevoPadre : Transform; transform.parent = nuevoPadre; Debug.Log(transform.parent.gameObject.name);//Esto ya lo teníamos escrito
Salvamos, y seleccionamos el cubo para que en el inspector quede expuesta la variable nuevoPadre. Arrastramos hasta ella la cámara principal desde la jerarquía y le damos al play. Nuestro cubo ha cambiado de padre. Pensemos, llegados a este punto, que nuestro transform hijo tiene una posición, rotación y escala relativa con respecto a su padre. Si cambiamos su padre, éstas obviamente cambiarán.
root: var root : Transform
Esta variable hace referencia al transform más alto de la jerarquía, por lo que guarda muchas similitudes con parent, pero referida al “parent de los parent” del que depende nuestro transform. Si éste tiene un padre sin padre, el resultado equivaldrá a parent. Si su padre tiene un padre que tiene un padre que tiene un padre, la variable irá escalando hasta llegar al último padre y devolverá su valor (o nos permitirá modificar dicho valor). Para el caso de que el objeto que invoca esta variable no tenga padre, lo que se devuelve no es null, sino el propio transform que la invoca.
childCount: var childCount : int
Es meramente una variable de tipo int que indica el número de hijos que tiene un transform.
lossyScale: var lossyScale : Vector3
Es esta una variable de tipo Vector3 que representa la escala global del objeto. Es una variable de sólo lectura, por lo que obviamente no podemos modificar la escala global (la local ya vimos que sí con localScale) asignando valores a esta variable.
13. CLASE TRANSFORM (IV)
FUNCIONES:
Translate: function Translate (translation : Vector3, relativeTo : Space = Space.Self) : void
Esta función nos permite mover el transform en la dirección y distancia indicados en el parámetro de tipo Vector3 traslation. El segundo parámetro nos permite decidir si el transform se moverá según sus propias coordenadas (locales) o lo hará en base al espacio global. Por defecto, si no indicamos el segundo parámetro, Unity entiende que el transform se moverá según sus propias coordenadas (Space.Self). Desarrollemos esto con un ejemplo: Para empezar, desemparentamos nuestro cubo respecto de la cápsula, arrastrándolo a un espacio vacío de la jerarquía. Acto seguido, con el cubo seleccionado, en el inspector le asignamos a la coordenada Y de rotación un valor de 35. Abrimos el script que tenemos asignado al cubo y tecleamos:
transform.Translate(Vector3.forward * 5);
Salvamos y presionamos play. El cubo avanza cinco unidades en su eje Y, y recalcamos lo de "su" eje, ya que al no añadirle un segundo parámetro que diga lo contrario, se mueve según sus coordenadas locales. Para ver la diferencia, añadamos como segundo parámetro a la función la variable World de la enumeración Space, para que sustituya al parámetro por defecto Space.Self:
transform.Translate(Vector3.forward * 5, Space.World);
function Translate (x : float, y : float, z : float, relativeTo : Space = Space.Self) : void Existe un segundo prototipo para la función Translate. Como se puede comprobar, se diferencia del primero en que en lugar de pasar como parámetro para establecer la dirección y longitud del movimiento un Vector3, se pasa cada eje como un float independiente.
function Translate (translation : Vector3, relativeTo : Transform) : void function Translate (x : float, y : float, z : float, relativeTo : Transform) : void Estos dos prototipos varían de los anteriores en el segundo parámetro, que ya no versa sobre si el movimiento se hará tomando en cuenta las coordenadas locales del objeto que se mueve o las globales de la escena. En cambio, aquí el movimiento de nuestro transform vendrá fijado por otro transform. De esta manera, nos moveremos de acuerdo con las coordenadas locales de ese segundo objeto al que hacemos referencia. Si por ejemplo el parámetro relativeTo es una cámara, la derecha del traslation no es la derecha local de nuestro transform, o la derecha global, sino la derecha de la cámara. Por ejemplo: Tecleamos lo siguiente en nuestro script
transform.Translate(Vector3.right * 5);
Esto hará que nuestro cubo se mueva cinco unidades a su derecha. Y ahora modificamos el script para que quede así:
var miCamara : Transform; transform.Translate(Vector3.right * 5, miCamara); Arrastramos la cámara principal hasta la variable miCamara. De nuevo al play. Ahora el cubo se mueve cinco unidades a la derecha de la cámara. Como último apundo hemos de añadir que si relativeTo es nulo, las coordenadas del movimiento pasarán a ser las globales. Rotate: function Rotate (eulerAngles : Vector3, relativeTo : Space = Space.Self) : void
Esta función permite rotar un transform. Acepta como parámetro un Vector3 con los grados de rotación en ángulos Euler. Por defecto, al igual que sucediera con la función translate, el transform rota sobre sus coordenadas locales, pudiendo hacerlo según las coordenadas globales si se lo indicamos con Space.World como segundo parámetro. Veamos un ejemplo. Rehagamos nuestro script con el siguiente contenido:
function Update() { transform.Rotate(Vector3.right * 25 * Time.deltaTime); transform.Rotate(Vector3.up * 20 * Time.deltaTime, Space.World); }
Al darle al play, observaremos que nuestro cubo rota sobre su eje X a razón de 25 grados por segundo mientras a la vez gira sobre el eje Y global a razón de 20 grados por minuto. Es de señalar que ambas instrucciones se encuentran contenidas dentro de la función Update. Esta función, que en su momento estudiaremos con la debida profundidad, es llamada cada frame por nuestro ordenador (el framerate de cada ordenador varía), de tal manera que el movimiento es contínuo, pues los valores de rotación de nuestro cubo son actualizados constantemente. Precisamente porque el framerate de cada ordenador es distinto, y para evitar que en función de cada PC los objetos se movieran más o menos deprisa (lo que tendría indeseables consecuencias, sobre todo en juegos en línea multijugador), se suele utilizar la variable de la clase Time Time.deltaTime. Time.deltaTime lo que consigue es transformar la unidad de frecuencia de cualquier tipo de movimiento de frames a segundos. Si por ejemplo en el último script no hubiéramos usado estas variables de tiempo, el cubo giraría 25 y 20 grados cada frame, quedando al albur de cada ordenador la frecuencia real que eso supondría. Al multiplicarlo por Time.deltaTime las rotaciones dependerán del tiempo y en consecuencia se producirán con la misma cadencia en cualquier ordenador. function Rotate (xAngle : float, yAngle : float, zAngle : float, relativeTo : Space = Space.Self) : void En esta versión de la función, la única diferencia es que se sustituye el Vector3 por tres floats, en los cuales se consignarán igualmente los grados de rotación. function Rotate (axis : Vector3, angle : float, relativeTo : Space = Space.Self) : void
La variante que nos ofrece este tercer prototipo es que por un lado se indica en el primer parámetro sobre qué eje queremos que rote el transform, y en un segundo parámetro de tipo float le hemos de indicar el número de grados ha de rotar.
14. CLASE TRANSFORM (V)
RotateAround: function RotateAround (point : Vector3, axis : Vector3, angle : float) : void
Esta función nos permite que nuestro transform rote alrededor de un punto (u objeto situado en ese punto), como si orbitara. El parámetro point sería el punto, descrito por un Vector3, alrededor del cual queremos hacer girar nuestro transform. Axis nos servirá para indicar sobre qué eje queremos girar, y angle el número de grados por frame (si está dentro de la función update) o por segundo (si implicamos la variable de clase Time.deltaTime) que queremos que gire. Obviamente, aquí estamos variando tanto la rotación como la posición de nuestro transform. si por ejemplo quiriéramos que nuestro cubo girara sobre su eje Y alrededor del centro exacto del espacio global, a razón de 20 grados por segundo, escribiríamos este script:
function Update() { transform.RotateAround (Vector3.zero, Vector3.up, 20 * Time.deltaTime); }
Dado que el cubo se hallaba ya en las coordenadas globales 0,0,0 el script anterior lo único que consigue es que el cubo parezca girar sobre sí mismo. Vamos a hace lo siguiente: colocamos en el inspector a nuestro cubo en position.x = -1, y volvemos a darle al play. Y vemos ya más claramente cómo orbita alrededor del punto dado, cambiando simultáneamente de posición y de rotación. Podemos hacer también con esta función que un objeto orbite alrededor de otro. Escribimos:
var centroDeGravedad: Transform; function Update() { transform.RotateAround (centroDeGravedad.position, Vector3.up, 20 * Time.deltaTime); } Acto seguido, arrastramos el objeto sobre el que queremos que orbite nuestro cubo, en este caso la cápsula. Le damos al play y comprobamos. Observemos que aunque arrastramos un gameobject, lo que está esperando nuestro scrips es un transform. Lo que sucede es que -tal como vimos en las primeras lecciones- todos los componentes de nuestro gameobject comparten nombre, de tal manera que cuando en casos como este Unity detecta que uno de los componentes del gameobject que le estamos arrastrando tiene el nombre y el tipo del que está esperando para cumplimentar una variable, automáticamente selecciona -en este caso- el transform homónimo y no el gameobject. Ese transform, no obstante, no es del tipo Vector3 que espera nuestra función. Sí que lo es la variable position del mismo, que además contiene las coordenadas globales del transform sobre el que queremos orbitar. Podéis probar a sustituir en el segundo parámetro Vector3.up por Vector3.right (o Vector3.forward), y comprobar qué es lo que sucede. LookAt: function LookAt (target : Transform, worldUp : Vector3 = Vector3.up) : void El nombre de esta función se podría traducir al castellano como "mira a", y es exactamente eso lo que hace: Rotar nuestro transform hacia un objetivo -parámetro target- que es asimismo de tipo transform (y no Vector3, como en el caso de la función anterior). Esa rotación se efectúa sobre un eje global, y por defecto éste será el eje Y. Ello implicará que nuestro transform, si no le indicamos lo contrario, girará hacia la derecha y la izquierda "mirando" al transform que hayamos designado como objetivo. Huelga decir que esta función se usa mucho para cámaras, cuando pretendemos que éstas sigan en todo momento a un determinado personaje. Es importante avisar de que para que el transform gire sobre el eje que le indiquemos, ha de estar totalmente perpendicular a dicho eje global. Vamos a la práctica. En el Proyecto, colocamos el ratón sobre la carpeta Mis Scripts y le damos al botón derecho=>create=>javascript. Al nuevo script lo renombramos como MiSegundoScript y le damos doble click para se se nos abra el editor. Desplazamos un poco hacia abajo la función Update, y la dejamos como sigue:
var sigueme : Transform;
function Update () { transform.LookAt(sigueme); } Salvamos, y la arrastramos hasta nuestra cámara en la jerarquía. Seleccionamos entonces la cámara y arrastramos ahora hasta la variable sigueme nuestro cubo. Previamente a darle al play nos aseguramos de que el otro script (MiPrimerScript) que tenemos vinculado al cubo, contenga lo siguiente:
var centroDeGravedad: Transform; function Update() { transform.RotateAround (centroDeGravedad.position, Vector3.up, 20 * Time.deltaTime); } Y ahora sí, ya podemos darle al play y perseguir a nuestro cubo. function LookAt (worldPosition : Vector3, worldUp : Vector3 = Vector3.up) : void Este segundo prototipo se diferencia del primero en que el objetivo que ha de seguir nuestro transform no es otro transform, sino un Vector3. Esto tiene sentido, siguiendo con el ejemplo de la cámara, si quisiéramos que ésta enfocara un punto concreto de la escena (en coordenadas globales) Por ejemplo, si queremos que una cámara enfoque al centro de la escena, le vinculamos este script:
transform.LookAt(Vector3.zero); Otra posibilidad que nos permite esta función es que sea nuestro jugador, desde su teclado, quien se encargue de decidir dónde ha de enfocar la cámara. Para ello tendremos que recurrir a una clase que aún no hemos visto, la clase input. Abrimos el script MiSegundoScript, y tecleamos lo que sigue:
function Update () { transform.LookAt(Vector3(Input.GetAxis("Horizontal") * 10.0,0,0)); }
Le damos al play y movemos la cámara con las flechas de desplazamiento horizontal. Quizás notemos que el resultado es un poco basto, pero meramente quería que tuviérais una idea de las utilidades de esta función. También podríamos controlar la cámara desde el ratón y no desde el teclado. Veremos todo esto en profundidad cuando estudiemos la clase input.
15. CLASE TRANSFORM (VI)
TransformDirection: function TransformDirection (direction : Vector3) : Vector3
Esta función toma como único parámetro la dirección local de un transform almacenada en un Vector3 y la convierte en dirección global, devolviéndola en otro Vector3.
La utilidad de esta función puede resultar un poco confusa al principio. Pensemos, para intentar aproximarnos al concepto, que quisiéramos hacer nuestra propia versión del FIFA 2011. Modelamos un campo de futbol y, para darle más realismo y emoción al juego, colocamos varias cámaras en los laterales del campo, cámaras que mediante LookAt irían siguiendo los lances del juego. El problema que nos encontraríamos es que cuando Messi (por poner un caso) está avanzando hacia delante (en las coordenadas globales), en cambio en nuestra cámara lateral pareciera que lo está haciento -por ejemplohacia la izquierda. Y en consecuencia, cuando intentamos que nuestro defensa lo ataje moviéndolo hacia atrás (según la perspectiva que nos da la cámara lateral) veremos consternados que el defensa en lugar de retroceder se desplaza hacia la derecha. Esto no nos pasaría si, gracias a esta función, convertimos la coordenada "hacia detrás" de nuestra cámara en su equivalente en coordenadas globales. Si esa función se la aplicamos a todas las cámaras, no tendremos que estar pensando "Ojo, que esta cámara está en el gol norte, por lo que si me baso en ella cuando haga un movimiento, he de recordar que arriba es abajo y viceversa y la derecha es la izquierda y bla, bla, bla". Veámoslo en un ejemplo. En Unity borramos el script MiSegundoScript de la cámara. Colocamos en el inspector a nuestro cubo en las coordenadas 0.0.0. con una rotación igualmente de 0,0,0. Asimismo, la cámara debería estar en las coordenadas de posición 0,1,-5 con los tres ejes de rotación a 0. Abrimos MiPrimerScript. Tecleamos esto:
var miCamara : Transform; var estaEsMiDerecha : Vector3; estaEsMiDerecha = miCamara.TransformDirection(Vector3.right); transform.Translate(estaEsMiDerecha * 3);
Arrastramos la cámara a miCamara. Le damos al play. El cubo se moverá 10 unidades a la derecha. Pero, ¿a la derecha de quién?. Si observamos, la derecha del cubo es también la derecha de la cámara. Para averiguarlo, vamos a recolocar la cámara en estas coordenadas: Position: -10, 1, -0.5 Rotation: 0, 90, 0 Y de nuevo le damos al play. Obviamente, el cubo se mueve a la derecha de la cámara. Visto desde la ventana game, coincidirá ahora "nuestra" derecha (entendiendo como tal la que nos muestra la pantalla) con el sentido el movimiento.
El script no necesita mucha explicación. Inicializamos una variable con el transform de la cámara que hemos arrastrado. La derecha de esa cámara la almacenamos en una variable de tipo Vector3, la cual luego pasamos como parámetro a nuestra función TransformDirection para que nos convierta la derecha de nuestra cámara en la derecha de las coordenadas globales. A partir de ahí, todo lo que le suceda a la derecha de nuestra cámara (por así decirlo) le estará pasando a la derecha del mundo. function TransformDirection (x : float, y : float, z : float) : Vector3 Es la misma función, pero aplicando como parámetros 3 floats para cada eje en lugar de un Vector3.
InverseTransformDirection: function InverseTransformDirection (direction : Vector3) : Vector3 o bien function InverseTransformDirection (x : float, y : float, z : float) : Vector3
Se trata obviamente de la función inversa a la anterior, y por consiguiente transforma una dirección global en dirección local. Veamos un ejemplo: Devolvemos antes que nada a nuestra cámara a su lugar y rotación originales: Position: 0,1,-5 Rotation: 0,0,0 La posición y rotación de nuestro cubo, por su parte, está totalmente a 0. Abrimos MiPrimerScipt, y tecleamos:
var estaEsLaDerechaDelMundo : Vector3; estaEsLaDerechaDelMundo = transform.InverseTransformDirection(Vector3.right); transform.Translate(estaEsLaDerechaDelMundo * 2);
Como vemos, declaramos una variable de tipo Vector3 que luego inicializamos de la siguiente forma: le pasamos a la función InverseTransformDirection el parámetro Vector3.right, que en esta función representa la derecha en coordenadas globales (no la derecha de ningún transform). Esa derecha del mundo, global, es "traducida" por la función en una coordenada local susceptible de usar por cualquier transform y es asignada,como decíamos, a nuestra variable estaEsLaDerechaDelMundo. Dicha variable, por último, es pasada como parámetro de movimiento al transform del gameobject que tiene vinculado el script (en este caso el cubo). ¿La probamos? El cubo se desplaza a la derecha. Pero, para saber si la derecha es la derecha del cubo o la derecha global, podemos en el inspector darle un valor al rotation.Y del cubo de 45 grados, por ejemplo. Y probamos de nuevo.
Definitivamente, el cubo se mueve ahora siguiendo el eje X global, y no el suyo local.
16. CLASE TRANSFORM (y VII)
TransformPoint: function TransformPoint (position : Vector3) : Vector3 function TransformPoint (x : float, y : float, z : float) : Vector3
A diferencia de TransformDirection, lo que esta función transforma de local en global es la posición y no la dirección. Esto es, esta función no versa sobre si un transform se desplaza a su derecha o a la derecha de las coordenadas globales, sino de si las coordenadas en que se encuentra el transform son globales (respecto al mundo) o locales (respecto al padre de dicho transform). Con esto entendido, como decimos, esta función acepta como parámetro las coordenadas locales de un transform (su ubicación respecto de la de su padre) y devuelve dichas coordenadas traducidas a coordenadas globales.
Lo veremos más claron con un ejemplo. Con el cubo en position 0,0,0 y rotation en 0,0,0 (si no,no
funcionará) arrastramos el cubo dentro de la cápsula en la Jerarquía, para convertir a cubo en hijo de la cápsula. Si seleccionamos el cubo, vemos en el inspector que sus coordenadas de posición han pasado a ser locales respecto de su padre. Abrimos MiPrimerScript (que debería seguir siendo parte del cubo) y escribimos el siguiente script:
var coordenadasLocales : Vector3; var coordenadasGlobales: Vector3; var coordenadasTransformadas: Vector3; coordenadasLocales = transform.localPosition; coordenadasGlobales = transform.position; coordenadasTransformadas = transform.position = transform.TransformPoint(transform.localPosition); Debug.Log("El cubo tiene las coordenadas locales " + coordenadasLocales.ToString() + " las globales " + coordenadasGlobales.ToString() + " y las transformadas " + coordenadasTransformadas.ToString());
El ejemplo parece más complicado de lo que es. Declaramos tres variables de tipo Vector3 para que contengan las tres coordenadas que imprimiremos para nuestro cubo (los nombres de las variables son lo suficientemente explicativos). Acto seguido inicializamos las dos primeras variables con la posición global y local del transform, y la tercera con las coordenadas que tendrá el transform cuando convirtamos sus coordinadas locales (respecto de la cápsula) en globales. Salvamos y le damos al play. Observamos que por un lado el cubo se desplaza en dirección y distancia opuesta al cilindro, ya que el -3,-4,-2 que constituían sus coordenadas locales ahora ha pasado a ser su posición en coordenadas globales.
InverseTransformPoint: function InverseTransformPoint (position : Vector3) : Vector3 function InverseTransformPoint (x : float, y : float, z : float) : Vector3
Función inversa a la precedente, que por tanto transforma la posición de un transform dado del espacio global al espacio local.
DetachChildren: function DetachChildren () : void
Es una función muy sencilla que sirve para desparentar los hijos. Para probarla debemos eliminar el script vinculado al cubo, y posteriormente hacer doble click en MiPrimerScript en el Proyecto para teclear lo siguiente:
transform.DetachChildren();
Salvamos y lo arrastramos a la cápsula, que es el transform que tiene hijos. Le damos al play y observaremos cómo en la Jerarquía el cubo deja de aparecer como hijo de la cápsula. Esta función es útil, entre otras cosas, cuando por alguna razón queramos destruir al objeto padre sin destruir a sus hijos: transform.DetachChildren(); Destroy(gameObject);
Find: function Find (name : String) : Transform
Con esta función podemos buscar por su nombre -y en su caso acceder- a un transform hijo de nuestro transform. La función devuelve dicho transform hijo, si lo encuentra. Si no lo encuentra, retorna null. Si tenemos que buscar a varios niveles, esto es, hijos de los hijos de nuestros hijos, podemos utilizar un slash o barra inclinada (“/”) para recrear la jerarquía donde queremos buscar (p ej. "Cuerpo/Brazo/Mano/Indice") Dado que deberíamos tener el cubo aún dentro de la cápsula, y MiPrimerScript vinculado a ésta, lo aprovecharemos para realizar un ejemplo:
var aquiMiHijo : Transform; function Update() { aquiMiHijo = transform.Find("Cubo"); aquiMiHijo.Rotate(Time.deltaTime*60, 0, 0); }
Como podemos comprobar al darle al play, la función Find encuentra un transform hijo llamado "Cubo" (recordemos que, por un lado hemos de suministrarle un string, esto es, no nos debemos olvidar de las comillas, y por otro lado que todos los componentes de un gameobject comparten por defecto el mismo nombre que éste, así que el transform del Cubo se llama Cubo), y almacena ese transform que encuentra dentro de la variable aquiMiHijo. A partir de ahí, podemos utilizar esa variable como si fuera un alias del propio transform.
IsChildOf: function IsChildOf (parent : Transform) : boolean
Esta función casi no requiere explicación. Devuelve true si el transform que hace la pregunta es hijo, "nieto" o incluso idéntico al transform parent que pasamos como parámetro. En otro caso, devuelve false.
Y con esto acabamos la clase transform, que como decía al inicio es una de las diez clases más importantes de la API de Unity.
17. CLASE RIGIDBODY (I)
Nos disponemos a estudiar otra de las clases vitales para entender el funcionamiento de Unity. Es raro en juego en el que no interviene de alguna manera la física (un enemigo se cae, un personaje salta, y coche choca contra un muro...) La clase Rigidbody controla la posición de un objeto a través de simulación de física. El componente Rigidbody, al ser añadido a un gameobject, toma el control sobre la posición de un objeto, haciendo que caiga bajo la influencia de la gravedad, y puede calcular cómo los objetos responderán a las colisiones. Vamos a inspeccionar unas cosas en Unity: antes que nada, si estáis siguiendo por orden estas lecciones tendréis (como yo) emparentado el cubo dentro de la cápsula, así que lo arrastramos fuera para desemparentarlo. Luego, con el cubo seleccionado, echamos un vistazo al inspector. Vemos en él diferentes apartados (su transform, su malla, un collider y elementos de renderizado) pero no un rigidbody. Lo mismo nos pasaría si examinamos la cápsula. Ambos objetos, por lo tanto, no están sometidos a leyes físicas, y en consecuencia es como si carecieran de masa y por ende fueran ajenos a la gravedad. Vamos a regalarles un rigidbody a nuestros objetos. Para hacer eso nos vamos -con uno de los obtetos seleccionado- al menú superior, y le damos a Component=>Physics=>Rigidbody. El gameobject que teníamos selecionado tiene ahora un componente nuevo:
Hacemos lo mismo con el otro gameobject. Acto seguido, en la ventana Escena pulsamos la X de nuestro gizmo,para tener una visión de los objetos desde el eje horizontal, y comprobar mejor lo que pasará ahora. Le damos al play...
y los objetos caen, porque ya no son inmunes a la gravedad. Lo que procede ahora, para evitar que nuestros objetos abandonen la escena a la primera de cambio, es colocar un suelo. Vamos a ello. Antes que nada, vamos a eliminar a nuestra vieja amiga la cápsula (conocida por los más veteranos por Origen). La razón es que por su forma específica no es demasiado estable para nuestros ejemplos una vez se ve afectada por la gravedad. Así que adiós, cápsula. Y como a rey muerto, rey puesto, nos vamos al menú y le damos a GameObject=>Create other=>Sphere. La renombramos como "Esfera". A continuación vamos a colocar a nuestros dos objetos a ras de suelo (antes no importaba que estuvieran flotando por ahí, pero ahora no querremos empezar el juego con un objeto pegándose un tortazo contra el suelo). Colocamos, como decimos, nuestros objetos en las siguientes coordenadas de posición (las rotaciones deben estar a 0): Cubo: 0,0,0. Esfera: 2,0,2. Nos vamos ahora al menú=>Gameobject=>Create Other=>Plane. Lo renombramos antes que nada como "Suelo". Luego colocamos nuestra ventana de escena en vista superior (click en eje Y del gizmo), y para acabar, en la coordenada Y de su transform.position escribimos -0.5. Esto último supongo que merece una explicación. Veamos, tanto el cubo como la esfera tienen una altura de una unidad y su origen (el 0,0,0) de dichos objetos se halla en su centro geométrico. Esto quiere decir que cuando un cubo está en el valor 0 de su eje Y, la mitad del cubo (0,5) se halla por debajo del nivel 0 (que es donde hemos colocado el suelo). En definitiva, si tanto el cubo como la esfera y el suelo los dejáramos con sus respectivos ejes Y a cero, los dos primeros aparecerían semienterrados en el tercero. Para evitarlo, o bien levantamos todos los objetos o bien bajamos el suelo, que es lo que hemos hecho. Démosle al play. Observamos que los objetos ya no se caen, aunque a decir verdad y siendo todos los elementos de nuestra escena del mismo color, quizás nos interesaría ponerle a nuestro suelo un color que los hiciera resaltar. Con el mouse situado sobre la carpeta Mis Scripts, pulsamos el botón derecho y creamos otro script (javascript, recordemos). Lo llamaremos ColorSuelo, y dice así:
function Start() { renderer.material.color=Color.red; }
La función Start es llamada por Unity, como su nombre indica, cuando se inicia el juego. Sólo es llamada una vez, a diferencia de Update, y en este caso nos interesa que sea así ya que vamos a dejar el color de nuestro suelo fijo durante todo el juego. Salvamos y arrastramos el script a Suelo. Le damos al play.
Y voilá, el suelo se nos vuelve de color rojo y los objetos contrastan algo más. Aún y todo, la escena continúa un poco oscura, así que mejor iluminarla un poco, ¿no? Introducimos en la escena desde el menú gameobject una luz direccional, que renombraremos como "Luz" y ubicaremos/rotaremos en las siguientes coordenadas: Posición: -5,15,-10 Rotación: 20,0,0 Si le damos al play, deberíamos ver algo parecido a esto:
Y más o menos con esto tendríamos ya la escena "preparada" para empezar a trabajar con la clase Rigidbody.
18. CLASE RIGIDBODY (II)
De un mero vistazo a este gráfico podréis sin duda deducir dos cosas: 1.- La clase Rigidbody es "hermana" de la clase Transform. Están al mismo nivel y heredan de las mismas clases. 2.- Nos va a ocupar un buen número de lecciones estudiarla. Pero el esfuerzo veréis que merece la pena, ya que dominando las clases Transform, Rigidbody (y Collider con sus dos clases hijas, que estudiaremos tras Rigidbody) tendremos controlado en gran parte el tema del movimiento y reacción ante el movimiento ajeno de nuestros gameobjects. Y sin más dilación, vamos allá. VARIABLES: velocity: Var velocity : Vector3
Representa a través de un vector la velocidad del rigidbody. No es aconsejable en la mayoría de casos modificar esta variable directamente, ya que derivaría en un comportamiento poco realista (no habría una transición entre la velocidad (o incluso inactividad) anterior y la nueva velocidad. Sí está justificado en cambio su uso para aquellos cambios de velocidad que provienen de un impulso, como por ejemplo un salto.
function FixedUpdate () { if (Input.GetButtonDown ("Jump")) { rigidbody.velocity = Vector3(0,6,0); } }
Tecleamos este script en MiPrimerScript, lo salvamos y lo arrastramos al cubo. Hay varias cosas del mismo que necesitarán una explicación. Para empezar, vemos que se está usando una función llamada FixedUpdate. Esta función es parecida a la función Update, de la que ya habíamos hablado, pero a diferencia de ésta, FixedUpdate es llamada (actualizada) a intervalos regulares (fijos). Por lo tanto, así como en la función Update la actualización depende del framerate de cada ordenador, en la función FixedUpdate la actualización se produce en intervalos fijos, iguales para todos los ordenadores. Por esa razón, cuando tratamos con cuestiones relacionadas con la física (y toda la clase Rigidbody está
relacionada con la física) habremos de usar la función FixedUpdate y no la función Update (salvo que queramos que las caídas, colisiones y velocidad de los objetos de nuestro juego sean diferentes en función del ordenador con el que juguemos o que, yendo aún más allá, en un juego online multijugador el personaje de cada user vaya a una velocidad distinta, por ejemplo.) Lo segundo nuevo del script es una referencia a la clase input. La clase input (otra de las clases que ocupan el top ten de importancia en Unity) es la que controla los eventos, esto es, qué tiene que hacer el usuario para disparar, qué tecla hace qué cosa, qué tipo de colisiones provocan que pase otra cosa distinta, etc. En este caso, Input.GetButtomDown(string) dispara un evento cuando el usuario pulsa la tecla que le pasamos a la función como un string. En nuestro Script le pasamos el string "Jump", que por defecto se corresponde a la barra espaciadora. ¿Y qué sucede cuando le damos a la barra espaciadora?. Pues que al rigidbody que en el capítulo anterior le añadimos a nuestro cubo le será aumentada de golpe la velocidad en el vector Y (arriba/abajo). Para comprobarlo, dale al play y cuando el juego esté corriendo presiona la barra espaciadora.
angularVelocity: Var angularVelocity : Vector3
Vector que representa la velocidad angular del rigidbody. Por velocidad angular nos referimos a la velocidad de rotación. Al igual que con velocity, en la mayoría de casos no cambiaremos la velocidad directamente, pues salvo casos en que precisamos un impulso súbito de la velocidad, no queda realista. Para entender bien la diferencia entre velocidad y velocidad angular, vamos a completar el script anterior, permitiéndonos aplicar una u otra velocidad, o incluso ambas a la par. MiPrimerScript quedará así:
function FixedUpdate () { if (Input.GetButtonDown ("Jump")) { rigidbody.velocity = Vector3(0,6,0); } if (Input.GetButtonDown ("Horizontal")) { rigidbody.angularVelocity = Vector3(7,0,0); } }
Como podemos observar, hemos añadido un segundo if de tal manera de que si el jugador en lugar de presionar la barra espaciadora (jump) presiona cualquiera de las dos flechas horizontales de dirección del teclado, la velocidad que se aplique al cubo será velocidad angular. Salvamos, le damos al play y pulsamos cualquiera de las flechas horizontales. Probablemente observaremos que el cubo hace intención de girarse, pero no tiene la fuerza suficiente para hacerlo. Si, en cambio, pulsamos la barra espaciadora y mientras el cubo está en el aire presionamos la flecha horizontal, asistiremos seguramente a una bonita pirueta de nuestro cubo.
19. CLASE RIGIDBODY (III)
drag: Var drag : float
Indica la resistencia al movimiento de un objeto y por lo tanto se usa para enlentecer dicho objeto. Cuando más algo el valor, mayor resistencia. Veámoslo añadiendo un tercer if a nuestro script, de tal forma que quede así:
function FixedUpdate () { if (Input.GetButtonDown ("Jump")) { rigidbody.velocity = Vector3(0,6,0); } if (Input.GetButtonDown ("Horizontal")) { rigidbody.angularVelocity = Vector3(7,0,0); } if (Input.GetKeyDown ("z")) { rigidbody.drag = 10; } }
Salvamos. La función GetKeyDown de la clase input dispara un evento cuando el user presiona una determinada tecla, en nuestro ejemplo la zeta. Le damos al play y lanzamos al aire nuestro cubo presionando la barra espaciadora, y cuando esté en el aire presionamos la z. Observaremos cómo los movimientos del cubo se enlentecen. Si a continuación volvemos a presionar la barra espaciadora o la flecha horizontal, comprobaremos que la resistencia al movimiento de nuestro cubo es mucho mayor.
angularDrag: rigidbody.angularDrag = 10;
Indica la resistencia angular del objeto. Puede ser usado para enlentecer la rotación de un objeto. mass: Var mass : float Variable que representa la masa del rigidbody, su peso. Recomienda el manual de referencia que el valor de la masa debería estar entre 0.1 y 10. Masas grandes -según el manual- hacen las simulaciones físicas inestables. Obviamente, los objetos con mayor masa proyectan a los que tienen menos masa cuando chocan. Pensemos en un camión grande golpeando un coche pequeño. Vamos a verlo con un ejemplo sencillo. Para llevarlo a cabo eliminamos el script que tiene vinculado nuestro cubo. Luego seleccionamos la esfera, y la ubicamos en las coordenadas -3,0,0. Acto seguido hacemos doble click en MiPrimerScript para abrir el editor y tecleamos lo que sigue:
function FixedUpdate () { if (Input.GetButtonDown ("Horizontal")) { rigidbody.velocity = Vector3(9,0,0);
} } Salvamos y arrastramos el script a la esfera. Este no tiene mucho misterio: al presionar la flecha horizontal del teclado, nuestra esfera debería tomar una velocidad de aceleración hacia su derecha, por lo que en principio debería impactar con el cubo. Probémoslo. Play. Observamos que efectivamente al pulsar la flecha de desplazamiento horizontal, la esfera golpea contra el cubo con la suficiente fuerza como para incluso volcarlo. Si nos fijamos, en los respectivos rigidbodies del cubo y la esfera que aparecen en el inspector, ambos tienen una mass de 1. Debido a ello, a raiz del impacto no hay un objeto que parezca "salir victorioso" de este (el cubo gira una cara y la esfera retrocede lentamente tras el impacto). ¿Que pasaría, no obstante, si uno de los dos rigidbodies tuviera una masa sensiblemente distinta del otro? Probémoslo añadiendo una expresión más a nuestro script:
function FixedUpdate () { if (Input.GetButtonDown ("Horizontal")) { rigidbody.velocity = Vector3(9,0,0); rigidbody.mass = 0.1; } } Ahora nuestra esfera tendrá una masa de 0.1, esto es, una décima parte de la masa del cubo. Salvamos, play, flecha lateral... Casi ni mueve el cubo. Podéis probar a la inversa, si queréis, darle más masa a la esfera (sin pasar de 10, recordad). Comprobad que una vez pulsais la flecha de desplazamiento horizontal, la masa de la esfera (si es que la tenéis seleccionada) cambia en el inspector y se convierte en la que habéis asignado en el script. Para acabar, advertiros de que un error común es asumir que objetos pesados caen más rápido que los ligeros. Esto no es verdad, ya que la velocidad depende de la gravedad y la resistencia (drag), y no de la masa. useGravity: Var useGravity : boolean Booleano que controla si la gravedad afecta a este rigidbody. Si está en false el rigidbody se comportará como si estuviera en el espacio. Probamos. Rehacemos MiPrimerScript:
function FixedUpdate () { if (Input.GetButtonDown ("Jump")) { rigidbody.velocity = Vector3(0,7,0); } if (Input.GetButtonDown ("Horizontal")) { rigidbody.useGravity = false; } if (Input.GetButtonDown ("Vertical")) { rigidbody.useGravity = true; } }
Salvamos, y le damos impulso hacia arriba a nuestra esfera. Alternad las flechas de desplazamiento horizontal y vertical para comprobar la diferencia de movimientos sin y con gravedad.
20. CLASE RIGIDBODY (IV)
isKinematic: Var isKinematic : boolean
Es un booleano que controla si las físicas afectan al rigidbody. Si isKinematic está habilitado (es true), a nuestro rigidbody no le afectarán ni fuerzas, ni colisiones ni junturas (joints). Ello quiere decir que no podemos esperar que frente -por ejemplo- a una colisión ese rigidbody kinemático se comporte de la manera lógica que por su masa, resistencia y gravedad debiera comportarse. Sus movimientos, reacciones y respuestas, por tanto, deberemos marcárselos específicamente mediante scripts o animaciones. Los cuerpos kinemáticos, por el contrario y a su vez, sí afectan el movimiento de otros rigidbodies no kinemáticos a través de colisiones o joints. Quisiera aprovechar para recordar que las variables "expuestas" de tipo booleano son mostradas por el inspector como un checkbox, donde el cuadro marcado se corresponde a true y el desmarcado al false.
freezeRotation: Var freezeRotation : Boolean
Esta variable vendría a ser una especialización de la anterior. freezeRotation controla si las físicas cambiarán la rotación del objeto. Si esta variable está habilitada (true), la rotación no será mofificada por la simulación de físicas, y en todo caso tendremos que establecerla nosotros manualmente mediante scripts o animaciones.
constraints: Var constraints : RigidbodyConstraints
Controla qué grados de libertad están permitidos para la simulación de nuestro rigidbody. Por defecto esta variable tiene el valor RigidbodyConstraints.FreezeNone, permitiendo rotación y movimiento en todos los ejes. En algunos casos, puedes querer constreñir un rigidbody para que sólo se mueva o rote sobre algunos ejes, como por ejemplo cuando desarrolles juegos en 2D. Puedes usar el operador de bits OR ('||') para combinar múltiples constraints. Vamos a probar con un sencillo ejemplo la utilidad de esta variable. En Unity alejamos un poco la cámara para que se vea tanto la esfera como el lateral izquierdo (según se mira) del plano. Abrimos nuestro script habitual:
function FixedUpdate () { if (Input.GetButtonDown ("Horizontal")) { rigidbody.velocity = Vector3(-6,0,0);
} }
Salvamos, play, flecha desplazamiento lateral. La esfera sobrepasa la superficie del plano y arrastrada por la gravedad cae. Podríamos solucionar esta eventualidad quitándole la gravedad a la esfera, pero tenemos una solución más elegante: no permitir que nuestra esfera se mueva en el eje Y (arriba/abajo). Así que modificamos nuestro script:
function FixedUpdate () { if (Input.GetButtonDown ("Horizontal")) { rigidbody.velocity = Vector3(-6,0,0); rigidbody.constraints = RigidbodyConstraints.FreezePositionY; } }
Y volvemos a probar. Dado que le hemos congelado ("freeze") a nuestra esfera la posibilidad de alterar el valor de su eje de posición Y, la esfera no puede ni ascender ni caer, razón por la cual continúa rodando allende el plano. Observamos que esta variable es de tipo RigidbodyConstraints, lo cual en el fondo es una enumeración que nos permite elegir entre una serie de valores, a saber:
None rotación FreezePositionX FreezePositionY FreezePositionZ FreezeRotationX FreezeRotationY FreezeRotationZ FreezePosition ejes. FreezeRotation ejes FreezeAll los ejes
Sin limitaciones de posición ni de Congela Congela Congela Congela Congela Congela Congela
la la la la la la la
posición posición posición rotación rotación rotación posición
en el en el en el sobre sobre sobre sobre
eje X. eje Y. eje Z. el eje X. el eje Y. el eje Z. todos los
Congela la rotación sobre todos los Congela rotación y posición de todos
collisionDetectionMode: var collisionDetectionMode : CollisionDetectionMode
Esta variable nos permite establecer el modo de detección de colisiones del rigidbody. Como podemos observar, es de tipo CollisionDetectionMode, tratándose ésta al igual que en el caso anterior de una enumeración que admite tres tipos de valores, que pasamos a desarrollar: Distrete: Este es el modo de detección de colisiones por defecto. Es el que menos recursos consume, pero no garantiza que nuestro rigidbody detecte un conjunto de colisiones, sobre todo si estas acontencen a gran velocidad. En modo discrete Unity comprueba si el rigidbody ha sido colisionado cada vez que se llama a la función fixedUpdate, que como ya explicamos es llamada un número fijo de veces por segundo. Si la colisión se produce en el impass entre una llamada a fixedupdate y la siguiente, nuestro rigidbody no la captará. Continuous: Con este modo on, nuestro rigidbody detectará las colisiones con cualquier malla geométrica estática que se tropiece en su camino, incluso si la colisión ocurre entre dos llamadas a FixedUpdate. Por malla geométrica estática Unity entiende cualquier MeshCollider (no tardaremos en estudiarlo) que no
tenga un rigidbody vinculado. ContinuousDinamic: Si establecemos este sistema de detección de colisiones para nuestro rigigbody, éste detectará colisiones tanto con mallas geométricas estáticas como con otros rigidbodies que tengan a su vez activado el modo continous collision detection. Este sistema consume bastantes recursos y debe ser sólo usado para prevenir movimientos muy rápidos de objetos. Sólo es soportado, además, por rigidbodies con sphere o box collider. A efectos de sintaxis, para colocar el sistema de detección de un rigidbody en modo ContinuousDinamic, por ejemplo, lo escribiríamos así:
rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
21. CLASE RIGIDBODY (V)
centerOfMass: CenterOfMass : Vector3
Representa el centro de masa relativo al origen del transform. El centro del transform se corresponde a las coordenadas locales 0,0,0, y si no reubicamos vía un script el centro de masa, Unity lo calcula automáticamente (suele coincidir con el centro del transform) Si bien no es una variable con demasiada utilidad, sí que a veces puede servirnos para, por ejemplo, hacer más estable y menos propenso a los vuelcos a un coche. Un coche con un centro de masa/gravedad bajo es menos propenso a volcar.
rigidbody.centerOfMass = Vector3 (0, -2, 0);
worldCenterOfMass: var worldCenterOfMass : Vector3
Esta variable nos indica el centro de masa/gravedad de un rigidbody en el espacio global. Tengamos presente que a diferencia de la anterior esta variable es de solo lectura.
detectCollisions: Var detectCollisions : Boolean
Indica si la detección de colisiones está habilitada (por defecto, sí). Deshabilitar esta variable es util si tenemos un objeto que es kinemático y queremos evitar que el sistema realice cálculos inútiles de detección de colisiones para él.
interpolation: Var interpolation : RigidbodyInterpolation
La interpolación nos permite suavizar el efecto de correr físicas en un framerate fijo. Por defecto la interpolación está off. Comunmente se usa la interpolación de rigidbodies en los personajes de los jugadores. Las físicas suelen transcurrir en pasos fijos (FixedUpdate), mientras los gráficos son renderizados en framerates variables. Esto puede llevar a que los objetos parezcan temblorosos, porque físicas y gráficos no están completamente sincronizados. El efecto es sutil pero perceptible, y sobre todo afecta a los personajes principales que son seguidos por alguna cámara. Es recomentable poner en on la interpolación para el personaje principal pero deshabilitarla para los demás. La enumeración RigidbodyInterpolation tiene los siguientes valores:
None defecto. Interpolate un Extrapolate a la
Significa que no hay interpolación. Es la opción por La interpolación irá con un poco de retraso, pero puede ser poco más suave que la extrapolación. La extrapolación predecirá la posición del rigidbody en base velocidad actual.
sleepVelocity: var sleepVelocity : float
La velocidad lineal, por debajo de la cual los objetos empiezan a dormir (por defecto 0.14) en un rango que va de 0 a infinity. La hecho de que los objetos duerman es una optimización que permite que el engine de físicas deje de procesar para estor rigidbodies. Así, cuando el objeto se duerme deja de detectar colisiones o realizar simulaciones, por ejemplo.
sleepAngularVelocity: var sleepAngularVelocity : float
La velocidad angular, por debajo de los cuales los objetos empiezan a dormirse (por defecto 0.14)
maxAngularVelocity: Var maxAngularVelocity : float
Representa la maxima velocidad angular del rigidbody (por defecto 7) en un rango que va de 0 a infinity. Se tiene que fijar una velocidad angular máxima para evitar inestabilidades numéricas con objetos rotando demasiado rápido.
Bueno, y hasta aquí las variables de la clase Rigidbody. He omitido la explicación de unas cuantas que me han parecido poco importantes, de poco uso.
22. CLASE RIGIDBODY (VI)
FUNCIONES: SetDensity: Function SetDensity (density : float) : void
Nos permite asignar una masa en base al collider vinculado, lo cual es útil para asignar una masa en unos valores acordes al tamaño de los colliders. Podemos aplicar un ejemplo similar al que realizamos con la variable mass. Abrimos nuestro script habitual y tecleamos:
function FixedUpdate () { if (Input.GetButtonDown ("Horizontal")) { rigidbody.velocity = Vector3(6,0,0); rigidbody.SetDensity(6.0); } }
Salvamos. Play. Presionamos la flecha de desplazamiento horizontal y comprobamos que la masa de la esfera le permite literalmente arrastrar al cubo.
AddForce: function AddForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void
Esta función (una de las más importantes de la API de Unity) añade una fuerza al rigidbody. Como resultado de esto el rigidbody comenzará a moverse. El primer parámetro es un Vector3, que nos servirá para indicar la potencia y dirección de la fuerza que aplicamos. Por ejemplo, podemos teclear en nuestro script de cabecera:
function FixedUpdate () { rigidbody.AddForce (Vector3.up * 10); }
Y al darle al play nuestra esfera ascenderá, fruto de una fuerza constante (aplicada en cada actualización de la función FixedUpdate). Obviamente, la velocidad de movimiento dependerá -además de la fuerza aplicada - de la masa del rigidbody y de la resistencia, esto es, varios rigidbodies diferentes tendrán reaccione diferentes cuando les es aplicada la misma fuerza. function AddForce (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void Este segundo prototipo de la función, como ya viene siendo habitual, meramente sustituye el parámetro de tipo Vector3 por 3 de tipo float.
//Equivale al script anterior function FixedUpdate () { rigidbody.AddForce (0, 10, 0); }
El segundo parámetro observamos que es de tipo ForceMode, que como posiblemente sospechéis es una nueva enumeración made in Unity, que tiene los siguientes valores:
Force masa. Es
Añade una fuerza contínua al rigidbody, dependiendo de su el valor por defecto y que obtiene resultados más realistas,
ya que como decíamos costará más esfuerzo mover objetos más pesados que Acceleration masa. A
más livianos. Añade una aceleración contínua al rigidbody, ignorando su diferencia de ForceMode.Force, Acceleration moverá cualquier rigidbody de la misma forma sin tener en cuenta su masa, lo
que es útil si sólo queremos controlar la aceleración de un objeto sin preocuparnos de la fuerza que debíeramos aplicarle para obtenerla Impulse cuenta
teniendo en cuenta su masa y resistencia. Añade un impulso puntual de fuerza al rigidbody, teniendo en su masa. Hay que tener la precaución de no incluir este modo
dentro de una función que se actualice a menudo (como FixedUpdate). Este modo es útil para aplicar fuerzas que acontecen de repente, como VelocityChange ignorando su
explosiones o colisiones. Añade un cambio de velocidad instantáneo al rigidbody, masa. Con esa salvedad (una misma fuerza tendrá los mismos resultados con rigidbodies de diferente masa), le es
aplicable lo mismo que al modo precedente.
Podemos ver la diferencia entre fuerzas aplicadas en el mismo script de arriba. Lo modificamos para que quede así:
function FixedUpdate () { rigidbody.AddForce (Vector3.up * 10, ForceMode.Impulse); }
Podéis ver que la esfera sale disparada. No retorna porque como la tenemos dentro de la función FixedUpdate está recibiendo un impulso periódico. Vamos a liberarla:
rigidbody.AddForce (Vector3.up * 10, ForceMode.Impulse);
Supongo que queda demostrada la diferencia entre fuerza constante e impulso.
AddRelativeForce function AddRelativeForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void
Añade una fuerza al rigidbody relativa al sistema local de coordenadas de dicho rigidbody. Para explicar qué quiere decir esto, vamos a preparar un ejemplo que requerirá varias fases: Para empezar, eliminamos el script que tenemos vinculado a la esfera. Nos colocamos en la vista vertical en la ventana Scene, haciendo click en la Y del gizmo. En Proyecto hacemos doble click en MiPrimer Script para abrir el editor, y tecleamos:
function FixedUpdate(){ rigidbody.AddForce (Vector3.right * 15); }
Salvamos, arrastramos el script al cubo y le damos al play. Tal como era de esperar, se aplica una fuerza lateral al cubo, pero éste da demasiadas vueltas sobre sí como para hacer una demostración respecto a las coordenadas de desplazamiento, así que vamos a completar el script con una de esas variables que cuando uno las estudia cree que no las usará nunca.
rigidbody.centerOfMass = Vector3(0,-0.3,0); function FixedUpdate(){ rigidbody.AddForce (Vector3.right * 15); }
Exacto. Le bajamos el centro de gravedad al cubo y en principio deberíamos ahora poderlo arrastrar como un mueble. Probemos si esto funciona en la práctica. Lo suficiente para poder realizar la explicación. Ahora giremos en el inspector el cubo 35 grados sobre el eje Y. Volvamos a darle al play. Vemos que la fuerza que aplica la función AddForce lo es en relación a las coordenadas globales, y no a las del objeto. Sustituimos en el script AddForce por AddRelativeForce y probemos de nuevo. Ahora la fuerza no es aplicada a la derecha del mundo, sino a la derecha relativa del cubo.
23. CLASE RIGIDBODY (VII)
AddTorque: function AddTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void function AddTorque (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void
Esta función añade una torsión (no he podido encontrar una traducción mejor para torque) al rigidbody. Como resultado el rigidbody empezará a girar alrededor del eje de torsión. Así, si al eje Y de rotación de nuestro cubo le reintegramos su valor de 0 y luego modificamos de la siguiente manera nuestro script del último ejemplo...
rigidbody.centerOfMass = Vector3(0,-0.3,0); function FixedUpdate(){ rigidbody.AddTorque (Vector3.right * 15); }
...vemos que efectivamente se le ha añadido a nuestro cubo una fuerza de torsión/rotación sobre el eje X. Recordemos que cuando hablamos de rotación, hemos de considerar a cada eje como si se tratara de una línea rígida alrededor de la cual nuestro objeto gira. Esto es, si quisiéramos que el cubo se moviera como una peonza, tendriamos que hacerlo rotar sobre su eje Y. Probad a cambiar "Vector.right * 15" por "Vector.up * 25". (Y si queremos que rote en dirección opuesta, le anteponemos un signo - al Vector.)
AddRelativeTorque: function AddRelativeTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void function AddRelativeTorque (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void
Como podréis suponer, esta función añade una fuerza de torsión relativa a las coordenadas locales/propias del rigidbody. Es a AddTorque lo mismo que AddRelativeForce a AddForce.
AddForceAtPosition: Function AddForceAtPosition (force : Vector3, position : Vector3, mode : ForceMode = ForceMode.Force) : void
Aplica fuerza a un rigidbody en una posición determinada.
AddExplosionForce: Function AddExplosionForce (explosionForce : float, explosionPosition : Vector3, explosionRadius : float, upwardsModifier : float = 0.0F, mode : ForceMode = ForceMode.Force) : void
Aplica una fuerza al rigidbody que simula el efecto de una explosión. La fuerza de la explosión decaerá de manera lineal con la distancia del rigidbody. Esta función también funciona bien con objetos inertes. Si el radio es 0, la fuerza plena será aplicada sin
importar cuan lejos se produzda la explosión del rigidbody. UpwardsModifier aplica la fuerza como si fuera aplicada desde debajo del objeto.
MovePosition: Function MovePosition (position : Vector3) : void
Mueve el rigidbody de posición. Por ejemplo:
private var velocidad : Vector3 = Vector3 (3, 0, 0); function FixedUpdate () { rigidbody.MovePosition(rigidbody.position + velocidad * Time.deltaTime); }
En este script declaramos primero una variable de tipo Vector3. Le anteponemos la palabra clave "private", que implica que dicha variable no será accesible desde el inspector (no quedará expuesta). Se declara como privada una variable que no queremos que se modifique desde la interface. El resto del script no merece mucha explicación. Se suma el Vector3 con la velociadd a la posición del rigidbody, y para que el desplazamiento se produzca en segundos y no en porciones de frames, lo multiplicamos por Time.deltaTime.
MoveRotation: function MoveRotation (rot : Quaternion) : void Rota el rididbody. Si observamos el prototipo de la función, vemos que hemos de pasarle a la misma un parámetro de tipo Quaternion. Ya hemos dicho en otras ocasiones que resulta bastante complejo para los humanos trabajar con cuaterniones, razón por la cual por norma general trabajaremos en grados Euler se los pasaremos a la función convenientemente traducidos. Un ejemplo:
private var velocidadEnAngulos : Vector3 = Vector3 (0, 100, 0); function FixedUpdate () { var velocidadEnCuaternionesPorTiempo : Quaternion = Quaternion.Euler(velocidadEnAngulos * Time.deltaTime); rigidbody.MoveRotation(rigidbody.rotation * velocidadEnCuaternionesPorTiempo); }
Como podemos comprobar, meramente tenemos que usar la función Quaternion.Euler, que recibe grados euler como parámetro y devuelve cuaterniones. El resto es sencillo.
24. CLASE RIGIDBODY (y VIII)
Sleep: Function Sleep () : void
Fuerza al rigidbody a dormir al menos un frame. Un uso común es llamar esta función al principio del juego para que el rigidbody no consuma recursos hasta que sea activado.
IsSleeping: Function IsSleeping () : Boolean
Booleano que devuelve true si el rigidbody está durmiendo.
WakeUp: Function WakeUp () : void
Fuerza al rigidbody a despertarse.
SweepTest: Function SweepTest (direction : Vector3, hitInfo : RaycastHit, distance : float = Mathf.Infinity) : Boolean
Devuelve true cuando un barrido del rigidbody intercepta algún collider. Esto funciona de la siguiente manera: nuestro rigidbody lanza en una dirección (parámetro direction de tipo Vector3) un rayo (pensemos hasta que no estudiemos la clase RaycastHit en una especie de láser o sonar de presencia, con la salvedad de que en este caso el rayo devolverá información de aquéllo con lo que ha chocado) hasta una determinada distancia (distance, de tipo float, que por defecto es infinito) y si el rayo tropieza con un collider, la función devuelve true. Obviamente, esto es de gran utilidad para, por ejemplo, que nuestro personaje tenga información para evitar colisiones. Veamos un pequeño ejemplo:
var rayo : RaycastHit; function Update () { if (rigidbody.SweepTest (-transform.right, rayo, 10)) { Debug.Log("Hay un obstaculo a " + rayo.distance + " metros a la izquierda"); } }
El script funciona como sigue: primero declaramos una variable de tipo RaycastHit para que recoja toda la información del collider que nuestra función pueda detectar en su barrido. Acto seguido lanzamos a través de la función SweepTest un rayo en dirección izquierda (-transform.right) hasta una distancia de diez metros. Caso de topar con un collider, distinta información del mismo será almacenada en la variable rayo
y la función devolverá true, con lo cual se imprimirá la información tecleada, indicando la distancia del collider hallado. Si le damos al play, observamos que nuestra función ha hallado un collider a la izquierda de nuestro cubo.
SweepTestAll: Function SweepTestAll (direction : Vector3, distance : float = Mathf.Infinity) : RaycastHit[] Es parecida a la anterior, pero devolviendo un array de tipo RaycastHit con toda la información de todos los colliders hallados en el camino
Además de las funciones tradicionales contenidas en la clase Rigidbody, ésta también tiene un tipo de función específica, que se disparan cuando acontece un evento. Unity las trata como "mensajes que envía la clase". Son tres: OnCollisionEnter: function OnCollisionEnter (collisionInfo : Collision) : void
OnCollisionEnter es llamada cuando nuestro rigidbody/collider toca otro rigidbody/collider. En contraste con la función OnTriggerEnter, a OnCollisionEnter se le pasa como parámetro una instancia de la clase Collision, la cual contiene información sobre puntos de contacto, velocidad de impacto, etc.
OnCollisionExit: function OnCollisionExit (collisionInfo : Collision) : void
OnCollisionExit es llamada cuando nuestro collider/rigidbody deja de tocar otro rigidbody/collider.
OnCollisionStay: function OnCollisionStay (collisionInfo : Collision) : void
OnCollisionStay es llamada una vez cada frame para cada collider/rigidbody que esté tocando nuestro rigidbody/collider.
25. CLASE COLLIDER (I)
Esta es la clase base para todos los tipos de colliders (que en castellano traduciríamos por "colisionadores"). Las clases BoxCollider, SphereCollider, CapsuleCollider y MeshCollider derivan de ella. Un collider vendría a ser la estructura que hace sólidos a los objetos. Seleccionemos en Unity la esfera. En el inspector observamos que tiene un Sphere Collider, que no es más que un collider con forma de esfera. Si desmarcamos el checkbox de dicho collider y le damos al play, automáticamente la esfera deja de ser sólida y, por efecto de la gravedad que le da el rigidbody, atraviesa el suelo y cae. Podemos seleccionar el cubo. Obviamente, el cubo no tiene una sphere collider, sino una box collider. Y si importáramos un gameobject capsule, tendría un capsule Collider. El problema viene cuando importamos un game object que no tiene una de estas formas primitivas. A veces se puede "encajar" una capsule collider en un árbol, o una box collider en un coche. A veces no necesitamos que el colisionador de un objeto coincida al 100% con dicho objeto y uno de estos colliders básicos nos pueden hacer el apaño. Pero hay veces en que, bien por la importancia del game object en el juego, bien por la forma compleja que tiene dicho game object, bien en la mayoría de casos por ambas cosas (pensemos en el ninja protagonista de nuestro juego, por ejemplo) necesitamos un collider que sea completamente fiel a la forma del gameobject. Para ello tenemos el mesh collider (que estudiaremos en breve), que es meramente la malla del objeto convertida en la estructura sólida del mismo que interactúa con el resto del mundo. Existe bastante confusión entre los nuevos en Unity respecto la diferencia entre un collider y un rigidbody. Un collider, como decimos, es meramente la superficie de nuestro objeto. Un rigidbody en cambio implica la aplicación de las leyes físicas a dicho objeto. Un objeto puede tener un collider y no un rigidbody (chocará con otros objetos, aunque no podremos controlar sus reacciones a las colisiones y será Unity quien se encargue de ellas automáticamente), y puede tener un rigidbody y no un collider (aunque entre otras cosas tendremos que desactivarle la gravedad, para que no atraviese los suelos). Obviamente, un
objeto puede tener un rigidbody y un collider, y de hecho Unity recomienda que si tu objeto es previsible que vaya a intervenir en muchas colisiones, además de un collider es recomendable añadirle un rigidbody kinemático. Vamos a ello: VARIABLES: enabled: var enabled : boolean
Si el collider está habilitado (true), colisionará con otros colliders. Se corresponde al checkbox que está en el inspector en el apartado del collider.
attachedRididbody: var attachedRigidbody : Rigidbody
Esta variable hace referencia al rigidbody vinculado a este collider, y permite acceder a él. Devuelve nulo si el collider no tiene rigidbody. Los colliders son automáticamente conectado al rigidbody relacionado con el mismo game object o con algún game object padre. Esto es, por ejemplo el rigidbody que le añadimos en su momento al cubo automáticamente quedó vinculado al box collider de éste. Probémoslo rehaciendo MiPrimerSript:
function FixedUpdate() { collider.attachedRigidbody.AddForce(0,15,0); }
Salvamos, nos aseguramos de que el script siga vinculado al cubo, y le damos al play.
isTrigger: var isTrigger : boolean
Si esta variable está habilitada (true) el collider se convierte en un trigger (lo podríamos traducir por "desencadenante" o "disparador"). Un trigger no colisiona con rigidbodies, y en su lugar envía los mensajes OnTriggerEnter, OnTriggerExit y OnTriggerStay (que estudiaremos al final de la clase) cuando un rigidbody entra o sale de él. De esta manera nos permite a nosotros diseñar de manera específica su comportamiento o las consecuencias de entrar en contacto con un trigger (pensemos en una puerta que nos lleva a otra dimensión, que se puede atravesar pero dispara un evento que nos teletransporta, por ejemplo)
material: var material : PhysicMaterial
El material usado por el collider. Si el material es compartido por varios colliders, al ser asignado a esta variable se hará una copia de dicho material que le será asignada al collider. La variable es del tipo PhysicMaterial, que como en su momento estudiaremos es una clase que nos permite manejar aspectos de los materiales como la fricción o la capacidad de rebote y el grado de la misma.
sharedMaterial: var sharedMaterial : PhysicMaterial
El material compartido de este collider. Modificando este material cambiaremos las propiedades de la superficie de todos los colliders que estén usando el mismo material. En muchos casos es preferible modificar en su lugar el Collider.material.
bounds: var bounds : Bounds
Los límites/bordes del collider en coordenadas globales.
26. CLASE COLLIDER (II)
FUNCIONES: ClosestPointOfBounds: function ClosestPointOnBounds (position : Vector3) : Vector3
Devuelve el punto más cercano de nuestro Collider con respecto a un punto dado. Esto puede ser usado para calcular puntos de choque cuando se aplique daño derivado de una explosión.
Raycast: function Raycast (ray : Ray, hitInfo : RaycastHit, distance : float) : boolean
Proyecta un rayo que devuelve true si tropieza con algún collider en la dirección y distancia indicadas. Consta de varios parámetros. El primero es de tipo Ray, y nos vamos a detener un momento en él. La estructura Ray nos permite crear una línea con un origen y una dirección. Consta de dos variables -de nombre origin y direction- que no son sino sendos Vector3 para representar el inicio y la dirección de dicha linea. Para verlo gráficamente, tecleamos el siguiente script:
function Update(){ var rayo : Ray; rayo.origin = transform.position; rayo.direction = transform.right; Debug.DrawLine(rayo.origin, rayo.direction); }
Al darle al play, observamos en la ventana de escena el rayo saliendo del cubo. Si queremos ver el mismo efecto en la ventana del juego, hemos de activar el botón gizmo que aparece encima de ésta, a su
derecha:
El segundo parámetro de la función Raycast -HitInfo- contendrá información sobre aquello con lo que golpeó el collider para el caso de que la función devuelva true, esto es, tropiece con algo. El tercer parámetro -distance- es obviamente la longitud del rayo.
OnTriggerEnter: function OnTriggerEnter (other : Collider) : void
Esta función y las que restan forman parte de la categoría de "mensajes enviados". Al estudiar la variable isTrigger decíamos que si nuestro collider habilitaba dicha variable, se convertía en un trigger (disparador) y dejaba de colisionar con otros rigidbodies, y que en lugar de responder a las físicas, al entrar en contacto (o dejar de tenerlo) con ellos lanzaba una serie de mensajes. OnTriggerEnter, así, es llamada cuando nuestro trigger entra en contacto con otros colliders, de tal manera que podemos escribir dentro de dicha función el código que defina lo que queramos que ocurra cuando se produce dicho contacto. Para explicarlo gráficamente, vamos a intentar hacer un script progresivo. Empecemos por eliminar el script que tenemos vinculado al cubo. Después, abrimos MiPrimerScript en Proyecto. Tecleamos lo siguiente:
function FixedUpdate(){ rigidbody.AddForce(4,0,0); }
Salvamos y arrastramos el script a la esfera. Tal como es de esperar, ésta recibe una fuerza hacia la derecha que le lleva a chocar con el cubo. ¿Qué sucede si nuestra esfera es convertida en trigger?. Probémoslo:
collider.isTrigger =true; function FixedUpdate(){ rigidbody.AddForce(4,0,0); }
Le damos al play y observamos que la esfera ha perdido toda solidez, de tal suerte que atraviesa el suelo y cae. Tenemos que evitar para nuestros fines que la esfera se mueva en el eje Y, evitando que caiga. Una variable que ya hemos estudiado viene en nuestra ayuda:
collider.isTrigger =true; rigidbody.constraints = RigidbodyConstraints.FreezePositionY; function FixedUpdate(){ rigidbody.AddForce(4,0,0); }
Ahora hemos preparado el terreno para lo que quería demostrar. De momento observamos que la esfera, en efecto, ya no cae (no puede operar en el eje Y), pero atraviesa -tal como era de esperar- el cubo. Vamos ahora a diseñarle un comportamiento específico a la esfera para cuando entre en contacto con el cubo.
collider.isTrigger =true; rigidbody.constraints = RigidbodyConstraints.FreezePositionY; function FixedUpdate(){ rigidbody.AddForce(4,0,0); } function OnTriggerEnter(loPrimeroQuePille) { loPrimeroQuePille.gameObject.renderer.material.color = Color.red; }
Démosle al play y luego la explicamos. Efectivamente, en el momento en que nuestro trigger/esfera topa con otro collider, lo valores del mismo son asignados al parámetro de la función OnTriggerEnter y podemos acceder a ellos para, como es el caso, por ejemplo cambiarle el color al cubo. Puede parecer un poco tortuoso todo el camino que nos lleva del collider loPrimeroQuePille hasta el color, pero con un poco de práctica y/o una guia rápida a mano, y conociendo el principio y el final del path, el resto es bastante sencillo. Fijémonos por otro lado en que este tipo de funciones no pertenecen propiamente a la clase, no usan un operador punto para vincularse a un objeto de una clase, sino que se usan de manera independiente, aunque para su activación requieren que efectivamente un collider con el trigger habilitado entre en contacto con algo. Es decir, su inclusión en la clase collider es más por "afinidad" que porque realmente formen parte de aquella.
OnTriggerExit: function OnTriggerExit (other : Collider) : void
Es llamada cuando el collider "other" deja de tocar con nuestro trigger.
OnTriggerStay: function OnTriggerStay (other : Collider) : void Es llamada casi todos los frames que el collider other esté tocando nuestro trigger.
OnCollisionEnter: function OnCollisionEnter (collisionInfo : Collision) : void
Es llamada cuando nuestro collider/rigidbody empiece a tocar otro rigidbody/collider. OnCollisionExit: function OnCollisionExit (collisionInfo : Collision) : void Es llamada cuando nuestro collider/rigidbody deja de tocar otro rigidbody/collider. OnCollisionStay: function OnCollisionStay (collisionInfo : Collision) : void Es llamada una vez por frame por cada collider/rigidbody que esté tocando nuestro rigidbody/collider.
27. CLASE MESHCOLLIDER
La clase MeshCollider, como vemos, hereda de la recién estudiada Collider, razón por la que hereda todas sus variables, funciones y mensajes que envía, amén de hacer lo propio con las clases Component y Object. Una MeshCollider (o colisionador de malla), tal como dijimos no hace mucho, es meramente la malla de un gameobject que convertimos en collider, por norma general porque nuestro gameobject tiene una forma en la que difícilmente se puede encajar una malla primitiva. Como ventajas de usar esta opción, obviamente que habrá una mayor coherencia entre la apariencia del gameobject y su superficie de colisión. Como mayor desventaja, el aumento de consumo para que nuestro ordenador haga los pertinentes cálculos para cada plano de nuestra malla.
VARIABLES: sharedMesh: var sharedMesh : Mesh
Hace referencia al objeto malla que nuestro gameobject está usando como detector de colisiones, si lo hay. Nos permite consultar sus características y/o asignar uno. La variable es de tipo mesh (malla) y la estudiaremos más adelante.
convex: var convex : boolean
Usa un collider convexo para la malla, esto es, con esta variable establecida en true nuestro collider de malla automáticamente pasa a ser convexo y todas las entradas y agujeros que pudiera tener nuestra malla desaparecen, permitiendo una detección de colisiones mucho más completa. Las mallas convexas pueden colisionar con otros colliders convexos y no convexos. Por lo tanto los colliders de malla convexos son apropiados para rigidbodies en el caso de que necesitáramos colliders con formas más detalladas que los colliders primitivos.
transform.collider.convex = true;
smoothSphereCollisions: var smoothSphereCollisions : boolean
Usa normales interpolados para colisiones de esferas en lugar de normales planos poligonales. Esto suaviza los baches para rodamientos de esferas sobre superficies suaves.
28. CLASE CHARACTERCONTROLLER (I)
Un character Controller nos permite hacer fácilmente movimientos limitados por colisiones sin tener que lidiar con un rigidbody. Comprobémoslo empíricamente, siguiendo estos pasos: 1.- Le borramos a la esfera el script que tiene vinculado. 2.- Le eliminamos a la esfera su componente rigidbody (botón derecho del ratón situado sobre el nombre de dicho componente=> remove component) 3.- Con la esfera seleccionada, vamos al menú Component=>Physics=>Character Controller. Nos saldrá un aviso preguntándonos si realmente queremos sustituir el collider primitivo que por defecto trae la esfera por un Character collider. Presionamos Replace. 4.- En Jerarquía seleccionamos suelo, y en el inspector desmarcamos el checkbox situado junto a Mesh Collider. 5.- Play.
El cubo, afectado por las leyes físicas, atraviesa el suelo al que le hemos deshabillitado la malla. Sin embargo el cubo, que ahora está controlado por el character controller, no se ve afectado por las leyes físicas. Antes de seguir, volvamos a habilitar la malla del suelo en su correspondiente checkbox. Un character Controller no se ve afectado tampoco por fuerzas y se moverá sólo cuando llamemos a la función Move, específica de esta clase.
VARIABLES: isGrounded: var isGrounded : boolean
Booleano que devuelve true si nuestro character controller (controlador de personaje, podríamos traducirlo) estaba tocando el suelo durante el último movimiento.
velocity: var velocity : Vector3
La velocidad relativa actual del character. La velocidad así retornada es la diferencia durante la última actualización entre la distancia antes y después de llamar a la función Move o SimpleMove, esto es, dónde estaba el último frame, o la última porción de frame o el último segundo el character antes de llamar a una de esas funciones y dónde estába despues, para que distancia partida por (tiempo, frame o fracción dada) sea igual a la velocidad por tiempo, frame o fracción dada. Decimos que la velocidad es relativa porque no puede seguir movimientos del transform que suceden fuera del character controller (por ej un character emparentado bajo otro transform en movimiento, como por ejemplo un vehículo moviéndose) collisionFlags: var collisionFlags : CollisionFlags Esta variable nos indicá qué parte de la cápsula (que es la forma que tiene un character controller) colisionó con el entorno durante la última llamada a CharacterController.Move. Comprobamos que la variable es de tipo CollisionFlags, que tal como podemos sospechar es de tipo enumeración, permitiéndonos usar los siguientes valores: None: No hay colisión. Sides: Colisión en los lados. Above: Colisión en la parte superior. Bellow: Colisión en la parte inferior. Así, un sistema para determinar si nuestro character controller ha topado con algo, y en qué parte de la cápsula, sería como sigue (no hace falta que tecleéis esto, es sólo a modo indicativo): function Update () { var controller : CharacterController = GetComponent(CharacterController); controller.Move(Vector3(1,0,0)); if (controller.collisionFlags == CollisionFlags.None) print("No hay colisiones"); if (controller.collisionFlags & CollisionFlags.Sides) print("Colisión lateral, al menos"); if (controller.collisionFlags == CollisionFlags.Sides)
print("Sólo colisión lateral, ninguna de otro tipo"); if (controller.collisionFlags & CollisionFlags.Above) print("Colisión superior, al menos"); if (controller.collisionFlags == CollisionFlags.Above) print("Sólo colisión superior, ninguna de otro tipo"); if (controller.collisionFlags & CollisionFlags.Below) print("Tocando tierra"); if (controller.collisionFlags == CollisionFlags.Below) print("Sólo tocando tierra, nada más"); } Le añadimos el script a la esfera (si no lo tenía ya vinculado) y le damos al play. La esfera se mueve hacia la derecha, hasta que impacta con el cubo (démonos cuenta que la esfera ya no tiene un ridigbody, pero gracias al character controller sigue colisionando con otros colliders. Debajo de la ventana game aparece impresa la última colisión, pero no es la única. Si le hacemos click al mensaje impreso nos saldrá una lista de todas las colisiones que ha sufrido nuestra esfera hasta topar con el cubo. La función GetComponent que hemos tecleado al inicio del script aún no la hemos estudiado, pero su cometido es bastante obvio: buscar el componente del gameobject que hace la llamada y que sea del tipo que le pasamos como parámetro, retornándolo. En este caso, nos sirve para inicializar la variable controller, con la que trabajaremos para montar un primitivo sistema de identificación de colisiones. la función Print es similar a Debug.Log, y la función Move la estudiaremos durante el próximo capítulo.
29. CLASE CHARACTERCONTROLLER (II)
radius: var radius : float
El radio de la cápsula del character controller.
height: var height : float
La altura de la cápsula del character controller.
center: var center : Vector3
El centro de la cápsula del character relativo a la posición del transform.
slopeLimit:
var slopeLimit : float
El límite de pendiente en grados por el que puede ascender nuestro character controller. Vamos a probar esta variable. Sigue estos pasos: 1.- Seleccionamos el cubo y lo desplazamos a la derecha (transform.x = 3, por ejemplo) 2.- Vamos al menú=>GameObject=>create other=>Cube. 3.- En el apartado transform, le damos al nuevo cube estos valores: position = 0,2,0 rotation = 0,0,315 scale = 0.2,7,1 Nos debería haber quedado algo así:
Vale, ahora tenemos una pasarela con una inclinación de 45 grados (360-315), por lo tanto, que nuestra esfera la pueda subir o no depende del valor que le demos a slopeLimit. Así, si tecleamos:
function Update () { var controller : CharacterController = GetComponent(CharacterController); controller.slopeLimit = 46.0; controller.Move(Vector3(1,0,0)); }
Nuestra esfera sí ascenderá por la rampa, ya que el grado de inclinación de esta es inferior al que puede asumir nuestro character controller. Pero le asignamos a slopeLimit el valor 45.0. observaremos que ya la esfera se niega a subir.
stepOffset: var stepOffset : float
Los límites de altura que el character controller podrá superar, fijados en metros.(no podrá por ejemplo subir una escalera si cada escalón mide más de ese límite) Para trabajar con un ejemplo, vamos a seleccionar el gameobject que nos sirvió como rampa en el ejemplo anterior. Le establecemos estos valores: position: 0,0,0 rotation: 0,0,90 scale: 0.6,3,1 Por otro lado, a nuestro cubo le vamos a aumentar momentáneamente la altura (scale.y = 4). Y tecleamos el siguiente código para nuestra esfera:
function Update () { var controller : CharacterController = GetComponent(CharacterController); controller.stepOffset = 0.3; controller.Move(Vector3(1,0,0)); }
Tomemos nota de que la antigua rampa tiene una altura de 0.6. En cambio, a nuestro character controller le hemos limitado la capacidad de superar alturas mayores de 0.3, por lo que cuando le demos al play...la esfera topará con la rampa. En cambio, si variamos el valor de stepOffset y lo colocamos, por ejemplo, a 0.9, la esfera no tendrá problemas en superar la rampa, aunque tras hacerlo topará con el cubo. Por experiencia personal, las mediciones de esta variable no siempre resulta exactas, ya que no todos los gameobjects se asientan completamente y de manera exacta sobre el suelo, o éste presenta desniveles. Para evitar este tipo de inexactitudes es necesario probar el comportamiento de los objetos y modificar stepOffsset en consecuencia, hasta dar con el comportamiento adecuado. Borramos la rampa y devolvemos al cubo a sus dimensiones habituales y a su posición 0,0,0.
detectCollisions: var detectCollisions : boolean
Determina si otros rigidbodies o character controllers colisionan con nuestro character Controller (por defecto esto está siempre habilitado). Esta variable no afecta a las colisiones que nuestro character controller sufre cuando se mueve, sino las que sufre a raíz del movimiento de otros character controllers o colliders, y en concreto a si esos colliders o character controllers entrantes deben ser bloqueados por nuestro controller. Para este ejemplo tenemos que escribir dos scripts. Abrimos MiPrimerScript, que deberíamos tener vinculado a nuestra esfera, y tecleamos:
function Update () { var controller : CharacterController = GetComponent(CharacterController); controller.detectCollisions = false; }
Aquí meramente le decimos a nuestro character controller que no queremos que bloquee los colliders entrantes. Y ahora abrimos MiSegundoScript y escribimos:
function FixedUpdate() { rigidbody.AddForce(-10,0,0); }
Le asignamos este segundo script al cubo, de tal forma que se desplazará hacia la izquierda, hasta colisionar con la esfera. Es preferible colocar la escena en vista superior (eje Y del gizmo). Le damos al play. Y tal como era de esperar, nuestra esfera no bloquea al cubo. Eso sí, si lo hubiéramos hecho al revés (mover la esfera hacia el cubo) aunque la variable detectCollisions estuviera en false, se produciría la colisión y el consiguiente bloqueo.
Y en la próxima lección vamos a por las funciones de esta clase.
30. CLASE CHARACTERCONTROLLER (y III)
FUNCIONES: Move:
function Move (motion : Vector3) : CollisionFlags
Esta función mueve un character controller en la dirección y velocidad establecida por el parámetro de tipo vector tres. Dicho movimiento sólo será restringido por las colisiones que sufra el character. Empecemos por un ejemplo básico. Eliminamos el script vinculado al cubo, y acto seguido modificamos el script vinculado a la esfera, para que quede así:
private var miCharCol : CharacterController; miCharCol = GetComponent(CharacterController); function Update() { miCharCol.Move(Vector3(1 * Time.deltaTime ,0,0)); }
No tiene mucho misterio. Almacenamos el componente character controller de nuestro gameobject en una variable (que declaramos como privada para que no sea accesible desde el inspector), y acto seguido le damos un movimiento de una unidad por segundo a la derecha. Pulsando el play efectivamente la esfera se desplaza hasta que topa con el cubo. El collider del cubo en este caso detiene el avance de la esfera, porque lo intercepta de pleno. Pero si al cubo lo ubicamos en position.Z= 0.8 y volvemos a darle al play, observamos que la esfera no detiene su avance. Choca y se desplaza de manera acorde a la colisión, pero acto seguido continúa avanzando hacia la derecha. La función devuelve una variable de tipo collisionFlags, que estudiamos hace un par de capítulos, y que aporta información sobre la ubicación de las colisiones que ha sufrido el character controller. Con esa información, podemos alterar el comportamiento de nuestro character controller cuando sufra una colisión en una zona determinada de su cápsula. Ampliamos el script anterior, para que quede así:
private var miCharCol : CharacterController; miCharCol = GetComponent(CharacterController); private var direccion : Vector3 = Vector3.right;
function Update() { var misGolpes : CollisionFlags = miCharCol.Move(direccion * Time.deltaTime); if(misGolpes == CollisionFlags.Sides) { direccion = Vector3(0,0,-1); } }
La esfera empieza su desplazamiento hacia la derecha (que es la dirección inicial que se le asigna a la función Move), hasta que colisiona con el cubo. Al hacerlo, nuestro condicional "if" detecta si la colisión se ha producido en uno de los laterales de la cápsula de nuestro character controller (CollisionFlags.Sides) y, para ese caso, se le pasa un nuevo vector3 a la dirección de la esfera.
SimpleMove: function SimpleMove (speed : Vector3) : boolean
Podríamos deducir que esta función es similar a la anterior, y así es, pero con algunas peculiaridades. Por ejemplo, si a nuestra esfera le asignamos el siguiente script...
function Update() { var miCharCol : CharacterController = GetComponent(CharacterController); miCharCol.SimpleMove(Vector3(1 * Time.deltaTime, 0,0)); }
...podríamos esperar que la esfera se moviera hacia la derecha, pero se da el caso de que a la que presionamos play la esfera atraviesa el suelo y cae. Esto es porque esta función automáticamente le asigna gravedad al character controller que la llama. Por otro lado, esta función no devuelve información sobre el lugar de contacto en que se ha producido las colisiones.
Y con esto acabamos una nueva clase.
31. CLASE RENDERER (I)
Un render vendría a ser el proceso por el que un ordenador muestra una imagen. De ahí que sea incuestionable la importancia de esta clase dentro de Unity. Tanto los gameobjects como algunos componentes tiene una propiedad renderer a la que podemos acceder/modificar, o que incluso se puede deshabilitar para hacer dicho gameobject o componente invisible.
VARIABLES: enabled: var enabled : boolean
Hace el objeto visible (true) o invisible (false).
castShadows: var castShadows : boolean
¿Proyecta sombras este objeto?
receiveShadows: var receiveShadows : boolean
¿Recibe sombras este objeto?
material: var material : Material
El material de nuestro objeto. Modificar esta variable sólo cambiará el material para este objeto. Si el material que asignamos a nuestro objeto está siendo usado para otros renders, se clonará el material compartido y se asignará una copia de dicho material para nuestro objeto. La variable es de tipo Material,que estudiaremos en su momento, y que nos permite cambiar el color del objeto, su textura y la manera en que reacciona a la luz, entre otras propiedades. Veamos un ejemplo sencillo. Le asignamos este script a la esfera:
renderer.material.color = Color.green; renderer.material.shader = Shader.Find( "Transparent/Diffuse" ); renderer.receiveShadows = false;
Al darle al play, podemos observar el cambio de apariencia.
sharedMaterial: var sharedMaterial : Material
Hace referencia al material que nuestro objeto comparte con otros. Modificar esta variabla cambiará la apariencia de todos los objetos que usen este material y las propiedades del mismo que estén almacenadas en el proyecto también, razón por la que no es recomendable modificar materiales retornados por sharedMaterial. Si quieres modificar el material de un renderer usa la variable “material” en su lugar.
sharedMaterials: var sharedMaterials : Material[]
Devuelve un array con todos los materials compartidos de este objeto, a diferencia de sharedMaterial y material, que sólo devuelven el primer material usado si el objeto tiene más de uno. Unity soporta que un objeto use múltiples materiales. Al igual que la variable anterior y por la misma razón, no es aconsejable modificar materiales devueltos por esta variable
materials: var materials : Material[]
Devuelve un array con todos los materiales usados por el renderer.
bounds: var bounds : Bounds
Es una variable de sólo lectura que indica los límites del volumen del renderer. A cada renderer Unity le asigna una caja invisible que contiene (que envuelve) al objeto y cuyos bordes están alineados con los ejes globales. De esta manera a Unity le resulta más sencillo hacer cálculos de desplazamiento y situación. Por ejemplo, renderer.bounds.center normalmente es más preciso para indicar el centro de un objeto que transform.position, especialmente si el objeto no es simétrico. Pensemos, para aproximarnos intuitivamente al concepto, en esas rosas que vienen dentro de cajas transparentes de plástico. En cualquier caso, la estructura Bounds la estudiaremos más adelante.
lightmapIndex: var lightmapIndex : int
El índice del lightmap aplicado a este renderer. El índice se refiere al array de lightmaps que está en la clase LightmapSettings. Un valor de 255 significa que no se ha asignado ningún lightmap, por lo que se usa el que está por defecto. Una escena puede tener varias lightmaps almacenados en ella, y el renderer puede usar uno o varios. Esto hace posible tener el mismo material en varios objetos, mientras cada objeto puede referirse a un ligthmap diferente o diferente porción de un lightmap.
isVisible: ar isVisible : boolean
Variable de sólo lectura que indica si el renderer es visible en alguna cámara. Hay que tener presente que un objeto es considerado visible para Unity cuando necesita ser renderizado en la escena. Podría por lo tanto no ser visible por ninguna cámara, pero todavía necesitar ser renderizado (para proyectar sombras, por ejemplo).
32. CLASE RENDERER (y II)
FUNCIONES: OnBecameVisible: function OnBecameVisible () : void
Esta función (del tipo mensaje enviado) es llamada cuando el objeto se vuelve visible para alguna cámara. Este mensaje es enviado a todos los scripts vinculados al renderer. Es útil para evitar cálculos que son sólo necesarios cuando el objeto es visible. Recordemos que la sintaxis de este tipo de funciones es distinta, así que si quisiéramos por ejemplo que un gameobject fuera visible cuando fuera a salir en una cámara, escribiríamos esto:
function OnBecameVisible() { enabled = true; }
OnBecameInvisible: function OnBecameInvisible () : void
Es llamada cuando el objeto ya no es visible por ninguna cámara.
33. CLASE MESHFILTER
Un mesh filter (o filtro de malla) toma una malla de la carpeta de assets y se la pasa a mesh renderer para renderizarla en la pantalla. Es, para entendernos, la estructura de alambre de la malla.
VARIABLES: mesh: var mesh : Mesh
Devuelve la instancia de malla asignada al mesh filter. Si no se ha asignado ninguna malla al mesh filter se creará y asignará una nueva. Si la malla asignada al mesh filter es compartida por otros objetos, automáticamente se duplicará y la
malla instanciada será retornada. Usando las propiedades de malla puedes modificar la malla para un solo objeto solamente. Los otros objetos que usan la misma malla no serán modificados. Cuando estudiemos la clase Mesh aprenderemos las diferentes posibilidades de alteración y deformación de mallas que esto nos permite.
sharedMesh: var sharedMesh : Mesh
Devuelve la malla compartida del mesh filter. Es recomendable usar esta función sólo para leer datos de malla y no para escribirlos, dado que podrías modificar los assets importados y todos los objetos que usan esta malla podrían ser afectados. Tengamos en cuenta también que no será posible deshacer los cambios hechos a esta malla.
34. CLASE JOINT (I)
Es la clase base para todos los tipos de joints. Por clase base queremos decir que no podemos instanciarla como tal, sino que sirve para ser heredada por diferentes tipos de joint (hingejoint, springjoint, characterjoint) que estudiaremos a continuación. Podríamos traducir joint por juntura o articulación, pero en principio usaremos la palabra en inglés. No obstante, tal como denota el significado en castellano de joint, podemos deducir que esta clase sirve para unir varios objetos de una manera u otra (pensemos en una puerta unida por una bisagra, en las articulaciones que mantienen unidos los huesos, o meramente en una pared de objetos-ladrillo.)
VARIABLES: connectedBody: var connectedBody : Rigidbody Esta variable referencia a otro rigidbody con el que este nuestra variante de joint conecta. Si la variable es null, el joint conecta el objeto con el mundo, esto es, en lugar de estar vinculado a otro rigidbody, nuestro objeto quedará "clavado" en su lugar en la escena. axis: var axis : Vector3 El eje altededor del cual el movimiento del rigidbody estará restringido, indicado en coordenadas locales. anchor: var anchor : Vector3
La posición del ancla alrededor de la cual el movimiento de los joints está restringido. La posición es definida en espacio local. En la próxima lección realizaremos una serie de ejemplos que nos permitirá comprender con mayor claridad la funcionalidad de estas variables.
35. CLASE JOINT (y II)
breakForce: var breakForce : float
La fuerza que es necesario aplicarle a este joint para romperlo. La fuerza podría venir por colisiones con otros objetos, fuerzas aplicadas con rigidbody.AddForce o por otros joints. Vamos a realizar una serie de ejemplos que incluirán tanto las variables vistas en la lección anterior como en ésta. Para ello vamos a seguir los siguientes pasos: 1.- Eliminamos en el inspector el hingejoint de la esfera, y a ésta le añadimos un sphere collider. La ubicamos en la posición (-1,0,-5). Le eliminamos el script que tenía vinculado. 2.-Vamos al menú gameobject=>Create other=>Cube. Ubicamos este cube en (-1,0,0) y le añadimos un hingejoint. 3.-Editamos el script MiPrimerScript como sigue:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.breakForce = 20;
4.-Asignamos MiPrimerScript al recién creado gameobject Cube, y luego arrastramos nuestro cubo a la variable pegadoATi. 5.-Editamos ahora MiSegundo script, tecleando:
function FixedUpdate() { rigidbody.AddForce(0,0,9); }
6.-Este script se lo asignamos a la esfera. Bueno, pues vamos ahora a jugar a los bolos más rupestres de la historia. Tenemos por un lado dos cubos unidos por un joint (el cubo que tiene el hinge joint y el cubo unido a éste primero a través de la variable connectedBody), con una fuerza de ruptura de 20, y una esfera dispuesta a chocar contra el cubo situado a nuestra izquierda con una fuerza de 9. Démosle al play y probemos qué sucede. Está claro que es mayor la resistencia del cubo que la fuerza de la esfera. Veamos si la variable breakForce ha tenido algo que ver en dicha resistencia cambiando su valor de 20 a 4. Play. Observamos que -aunque con algo de esfuerzo- ahora sí la esfera rompe el joint y consigue arrastrar el cubo de la izquierda (el portador del hingejoint). Hagamos ahora unas pequeñas modificaciones: Ubicamos la esfera en (0,0,-5) para que nos vaya a impactar al cubo que no tiene asignado el hingejoint. Para hacer un acercamiento intuitivo al ejercicio, podemos pensar que el cubo situado a nuestra izquierda (cube) sería el marco y el de nuestra derecha (nuestro cubo) la puerta. Visto así, ahora vamos a intentar impactar nuestra esfera con la puerta. Restablecemos la variable breakForce a 20. Le damos al play. Veremos que la esfera arrastra al cubo (abre la puerta), pero no rompe su unión con el otro cubo. Cambiemos de nuevo la variable breakForce de valor, pongámmosle un 1. De nuevo play. Observad cómo la esfera no tiene problemas en romper la juntura. Pero vamos a seguir jugando con las variables que hemos ido viendo en estos dos capítulos. Antes que nada desplazaremos un poco nuestra esfera para que sólo golpee en el cubo que no tiene el hinge joint, así que ubicamos la esfera en (0.5,0,-5) Vamos a añadir luego la variable axis a MiPrimerScript, de la siguiente manera:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.axis = Vector3.up; hingeJoint.breakForce = 20;
Recordemos que axis indica el eje alrededor del cual el movimiento de nuestro rigidbody portador del hingejoint permanecerá bloqueado. Por lo tanto, si bloqueamos el eje up/down a través de la variable axis, teóricamente el cubo de nuestra izquierda no debería girar sobre dicho eje. Como truco os recomendaría que antes de darle al play seleccionárais en el inspector el Cube, para que veáis la flecha naranja saliendo del gameobject. Al darle al play, la flecha señalará en la dirección del eje indicado por la variable axis. Pulsamos el play, decimos, y tal como esperábamos, la esfera pasa a través del cubo de nuestra derecha, pero el que tiene el hinge joint no gira sobre su eje vertical. La diferencia resulta muy clara si cambiamos en lugar de Vector3.up colocamos Vector3.forward como valor de axis. probadlo sin miedo. Probemos ahora la variable anchor, que tal como indicábamos indica la posición del ancla alrededor de la cual se restringe el movimiento de los joints. Probemos a dejar MiPrimerScript así:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.axis = Vector3.right; hingeJoint.anchor = Vector3(0,0,0); hingeJoint.breakForce = 20;
Hemos cambiado, como vemos, el eje que restringirá la variable axis, pasando del eje arriba/abajo al derecha/izquierda. Ubicamos la variable anchor en las coordenadas locales de nuestro hingejoint (0,0,0).
Démosle al play y observemos por un lado la ubicación de la flecha naranja (recordemos previo pulsar play dejar seleccionado Cube) y por otro el comportamiento de ambos cubos al ser impactados por la esfera. Vale. Ahora vamos a modificar la ubicación del ancla. Dejémosla así:
hingeJoint.anchor = Vector3(0,-0.5,0.5);
Eso significa (recordemos que estamos en coordenadas locales del cube) que el ancla estará situada en el centro del cube respecto del eje X (0), en la parte inferior del cube respecto del eje Y (-0.5) y en la parte trasera del cube respecto al eje Z (0.5). Démosle al play y observemos la situación del ancla y la diferencia de comportamiento.
breakTorque: var breakTorque : float
La torsión que es necesario aplicar a este joint para romperlo.
FUNCIONES: OnJointBreak: function OnJointBreak (breakForce : float) : void
Función de tipo "mensaje enviado" que es llamada cuando un joint vinculado al mismo game object se rompe. Cuando una fuerza que es mayor que el breakForce del joint es aplicada, el joint se romperá. En ese momento, esta función será llamada y el la fuerza de ruptura aplicada al joint le será pasada a la misma. Después de OnJointBreak el joint automáticamente será borrado del game object. Adaptemos MiPrimerScript para explicar esta función:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.breakForce = 1.0; function OnJointBreak(resistencia : float) { Debug.Log("Se nos rompió el joint con resistencia " + resistencia); }
El script es bastante sencillo. Le aplicamos a nuestro hingejoint una fuerza de ruptura baja, para asegurarnos de que el impacto de la esfera lo romperá. Cuando esto suceda será llamada la función OnJointBreak, que en este caso imprimirá un mensaje en el que, además de un texto, mostrará el valor que tendrá tras la ruptura la variable que le pasamos como parámetro, y que coincidirá con la fuerza de ruptura previamente asignada. Bueno, pues explicada la clase base, iremos a por las diferentes clases que heredan de joint.
36. CLASE HINGEJOINT (I)
Esta clase agrupa juntos 2 rigidbodies, constreñiéndolos a moverse como si estuvieran conectados por una bisagra. Este tipo de joints es perfecto para puertas, pero también puede usarse para modelar cadenas, por ejemplo.
VARIABLES: motor: var motor : JointMotor
La variable motor aplicará una fuerza en aumento hasta conseguir la velocidad deseada en grados por segundo. Esta variable es de tipo JointMotor, que es una estructura que a su vez tiene las siguientes variables: targetVelocity: Es la velocidad de desplazamiento o rotación que se pretende conseguir. force: Es la fuerza que aplicará el motor para conseguir la velocidad fijada en targetVelocity. freeSpin: Es un booleano. Si está habilitado, el motor sólo acelerará y nunca reducirá. Si unimos intuitivamente los conceptos junturas y motor, posiblemente se nos vengan a la cabeza engranajes, vehículos o maquinarias. Esa intuición no va muy desencaminada. Hagamos las siguientes modificaciones para prepararnos el terreno.
Eliminamos el script vinculado a la esfera. Luego vamos a colocar a nuestros cubos siameses (cubo y cube) dos unidades sobre tierra, lo que obviamente conseguiremos colocando su posición (la de los dos) en el eje X en 2. En ambos casos, además y para evitar que se nos caigan al suelo por la gravedad, en sus respectivos constrainst marcaremos Freeze Position Y. Ahora editamos el script MiPrimerScript (que si hemos seguido las lecciones al pie de la letra debemos tener vinculado a Cube), para que luzca así:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.anchor = Vector3(0,0,0); hingeJoint.motor.force =100; hingeJoint.motor.targetVelocity = 90; hingeJoint.motor.freeSpin = true;
Resumamos lo que hemos hecho: El rigidbody del segundo cubo (el que no tiene asignado un hingeJoint) es asignado a la variable pegadoATi, que posteriormente es conectada al hingeJoint. El ancla entre ambos cubos (que por defecto se ubica en el eje X) la colocamos en el centro geométrico del cubo, para que el giro que pretendemos sea más natural. Luego aplicamos un motor al hingejoint con una fuerza de 100 hasta conseguir alcanzar una velocidad de 90 grados por segundo, y sin que el motor pueda reducir. Démosle al play para comprobar que funciona. El motor trata de alcanzar la velocidad angular de motor.targetVelocity en grados por segundo. El motor sólo será capaz de alcanzar motor.targetVelocity si motor.force es suficientemente grande. Si el joint está girando más rápido que el motor.targetVelocity el motor se romperá. Un velor negativo para motor.targetVelocity hará que el motor gire en dirección contraria. El motor.force es la torsión máxima que el motor puede ejercer. Si es cero el motor se deshabilita. El motor frenará cuando gire más rápido que motor.targetVelocity sólo si motor.freeSpin es false. Si motor.freeSpin es true el motor no frenará.
37. CLASE HINGEJOINT (y II)
limits: var limits : JointLimits
Variable que establece/indica los límites de nuestro hingejoint (recordemos, para que tengamos más o menos claro a qué nos referimos con lo de límites, que hinge lo podemos traducir por "bisagra"). La variable es del tipo JointLimits, que es una estructura que a su vez tiene las siguientes cuatro variables: min: Es el límite inferior del joint. Cuando el ángulo o posición del joint se halla por debajo de este valor, el joint recibe la fuerza necesaria para restablecerlo a ese mínimo. max: El límite superior del joint. Si el ángulo o posición de éste está por encima de este valor, el joint recibe fuerzas pra restablecerlo a dicho máximo. minBounce:La capacidad de retorno del joint a su límite inferior cuando es golpeado por debajo de éste (pensemos en las puertas del Saloon. maxBounce:Lo mismo que la anterior aplicado al límite superior.
El joint será limitado para que el angulo esté siempre entre limits.min y limits.max. Dicho ángulo se calcula en términos relativos al ángulo existente al principio de la simulación, esto es, si entre dos rigidbodys hay ya al inicio un ángulo de 20º, si su variable limits.min la fijamos en 10, el ángulo real será de 30º Vamos a trastear un rato, que es la mejor manera de quedarse con los conceptos. De entrada, vamos a devolver al suelo a nuestros cubos, así que devolvemos sus transform.position.y respectivas a cero, y no nos olvidemos de desmarcar la casilla de sus constraints que marcamos en el ejemplo anterior. Por otro lado, el script MiSegundoScript, que en el anterior capítulo le habíamos sustraido a la esfera, se lo volvemos a asignar, para que de nuevo impacte contra nuestros cubos. Le daremos algo más de fuerza (AddForce(0,0,20)). Ubicaremos a la esfera en la posición 0.3,0,-5. Ahora vamos a intentar convertir los cubos en lo más parecido a una puerta, y le vamos a dar unos ángulos máximo y mínimo de apertura. Para evitar que el cubo que tiene asignado el hinge joint (cube) se nos mueva demasiado le asignaremos un valor a su variable mass en el rigidbody (en el inspector ) de 6. Y tecleamos en MiPrimerScript:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi; hingeJoint.axis = Vector3.up; hingeJoint.anchor = Vector3(0.5,-0.5,0.5); hingeJoint.limits.min = 0; hingeJoint.limits.max = 70; hingeJoint.limits.minBounce = 0; hingeJoint.limits.maxBounce = 0;
Si lo probamos, veremos que al impacto de la esfera nuestro joint alcanza un grado máximo de 70 grados, sin rebote. Si quisiéramos obtener un rebote de nuestra puerta precaria, podríamos asignarle a maxBounce un valor de 4, por ejemplo.
spring: var spring : JointSpring
Esta variable intenta alcanzar un ángulo que es su objetivo a base de añadir fuerzas de resorte y amortiguación. Podemos traducir en este contexto spring como muelle, lo que nos da una idea del tipo de impulso del que estamos hablando. El tipo de esta variable es JointSpring, que tal como podemos suponer es de nuevo una estructura, que en este caso consta de tres variables: spring: La fuerza tipo resorte para alcanzar la targetPosition, el ángulo deseado. Un valor más grande hace que alcancemos la posición más rápido. damper: La fuerza del amortiguador usada para amortiguar la velocidad angular. Un valor más grande hace que alcance el objetivo más lento. targetPosition: La posición que el joint intenta alcanzar. El spring alcanza la targetPosition en ángulos relativos al ángulo inicial.
Pongámoslo con un ejemplo. Para ello previamente eliminamos el script vinculado a la esfera. Y luego, MiPrimerScript debería quedar como sigue:
var pegadoATi : Rigidbody; hingeJoint.connectedBody = pegadoATi;
hingeJoint.axis = Vector3.up; hingeJoint.anchor = Vector3(0.5,-0.5,0.5); hingeJoint.spring.spring = 20; hingeJoint.spring.damper = 6; hingeJoint.spring.targetPosition = 90; Aquí abriremos nuestra puerta precaria con un impulso inicial de 20 y una amortiguación de 6, tendiendo a alcanzar los 90 grados. Si le damos al play notaremos el evidente efecto catapulta. useMotor: var useMotor : boolean Habilita/deshabilita el motor del joint. useLimits: var useLimits : boolean Habilita/deshabilita los límites del joint. useSpring: var useSpring : boolean Habilita/deshabilita el resorte del joint. velocity: var velocity : float Devuelve la velocidad actual del joint en grados por segundo. angle: var angle : float Devuelve el ángulo actual en grados del joint con respecto a la posición inicial. Es una variable de solo lectura.
38. CLASE SPRINGJOINT
El spring joint ata juntos dos rigid bodies que se encuentran a una cierta distancia. Las fuerzas spring (resorte) se aplicarán automáticamente para mantener el objeto a la distancia dada, intentando mantener la distancia que había cuando empezó. Las propiedades minDistante y maxDistance añaden unos límites de implícita distancia.
VARIABLES: spring: var spring : float
La fuerza del spring usada para mantener los dos objetos juntos.
damper: var damper : float
La fuerza de amortiguación usada para amortiguar la fuerza del spring.
minDistance: var minDistance : float
La distancia mínima entre los rigidbodies relativa a su distancia inicial. La distancia que habrá de mantenerse se conservará entre minDistance y maxDistance. Ambos valores son relativos a la distancia entre los respectivos centros de masa cuando la escena fue cargada al principio.
maxDistance: var maxDistance : float
La distancia máxima entre los rigidbodies relativa a su distancia inicial.
Veamos todo esto con un ejemplo rápido, para lo cual previamente haremos las pertinentes modificaciones en la interfaz de Unity. Asignamos (una vez más) el script MiSegundoScript a la esfera. Seleccionamos "Cube" en la jerarquía, y lo ubicamos en position.x = -3. Asimismo le eliminamos el componente hingejoint y en components=>Physics=>spring joints le añadimos un spring. Observaremos que ahora el cube en lugar de una flecha tiene un minicubo. Y -ahora sí- remozamos MiPrimerScript:
var pegadoATi : Rigidbody; var springJoint : SpringJoint; springJoint = GetComponent(SpringJoint); springJoint.connectedBody = pegadoATi; springJoint.spring = 25; springJoint.damper = 4; springJoint.minDistance = 0; springJoint.maxDistance = 0;
Veamos lo que hemos querido hacer. En primer lugar declaramos una variable (que la llamamos springJoint por claridad didáctica, pero que hubiéramos podido darle otro nombre legal) de tipo SpringJoint y le asignamos el componente SpringJoint de nuestro cube. Luego vinculamos el cubo a nuestro joint. Y a partir de ahí le damos al sprint/resorte una fuerza de recuperación de 25, una de amortiguación de 4, y establecemos la distancia mínima y máxima la que ya ambos cubos tenían al darle al play, esto es, 3 metros. Si quisiéramos que los cubos tras el impacto de la esfera quedaran a más o menos distancia de la inicial retocaríamos respectivamente minDistance o maxDistance. Dadle al play. Nuestro muelle retiene al cubo a la distancia estipulada del portador del spring joint. Podéis jugar con spring y damper para que esa fuerza de tipo resorte sea más o menos acusada.
39. CLASE CHARACTERJOINT
Esta clase, como su nombre indica, es principalmente usada con personajes. En concreto, la utilidad básica de la misma consiste en simular lo que se vienen llamando efectos Ragdoll (proceso de animación para simular muertes, valga la aparente paradoja). Ragdoll podría traducirse como "muñeca de trapo", para que nos hagamos una idea del efecto que se pretende conseguir.Esta especialidad de joint nos permite limitar el joint de cada eje.
VARIABLES: swingAxis: var swingAxis : Vector3
El eje secundario alrededor del cual el joint puede rotar. Los límites de rotación permitida alrededor de este eje vendrán establecidos por CharacterJoint.swing1Limit.
lowTwistLimit: var lowTwistLimit : SoftJointLimit
El límite menor del ángulo permitido alrededor del eje primario del characterjoint. El límite es relativo al ángulo de los dos rigidbody con el que comenzó la simulación. La variable es de tipo SoftJointLimit, que es una estructura con estos valores:
limit: El límite de posición u ángulo del joint. spring: Si es mayor que cero, el límite es soft. El spring retornará el joint a su posición inicial.
damper: Si spring es mayor que cero, el límite es soft. bounciness: Cuando el joint golpea el límite, esta variable puede establecerse para impedir un rebote.
highTwistLimit: var highTwistLimit : SoftJointLimit
El límite superior altededor del eje primario del characterjoint. El límite es relativo al ángulo de los dos rigidbody con el que comenzó la simulación.
swing1Limit: var swing1Limit : SoftJointLimit
El límite alrededor del eje primario del characterjoint. El límite es simétrico, así que un valor de 30 limitaría la rotación entre -30 y 30. El límite es relativo al ángulo de los dos rigidbodys con que empezó la simulación.
swing2Limit: var swing2Limit : SoftJointLimit
El límite alrededor el eje primario del joint del character.
40. CLASE BEHAVIOUR
Para comprender el sentido de la clase Behaviour he considerado que lo mejor era traer a la vista de nuevo el organigrama general que estuvimos comentando en las primeras lecciones. Si os fijáis ya hemos estudiado todas las clases que derivaban de Component, a excepción de ésta, de la que a su vez deriva otra serie de clases (que son las que estudiaremos a continuación).
Explico todo esto porque la clase Behaviour, como tal, sólo cuenta con una variable (con independencia, obviamente, de las que hereda de Object y Component). Dicha variable es enabled: var enabled : boolean
Como podemos imaginar, esta variable solamente habilita o deshabilita el objeto Behaviour (y/o los objetos derivados de esta clase) de un gameobject. Dicho de otra forma, tenemos por un lado las clases que derivan directamente de Component (las que ya hemos estudiado) y por otro las que derivan de Behaviour, que heredan lo mismo que las anteriores, más una variable que les permite deshabilitarse. Esto es, la diferencia entre, por ejemplo, transform y camera es que la segunda se puede desactivar. Esto lo podemos comprobar en el inspector, con la main camera seleccionada: veremos que Camera cuenta con un checkbox (el equivalente a la variable enabled) y Transform no. Os suelto este rollo para que entendáis que las clases que explicaremos a continuación son en esencia de raíz idéntica a las últimas que hemos estudiado, con la peculiaridad indicada.
41. CLASE MONOBEHAVIOUR (I)
Como podréis deducir de un mero vistazo a las funciones contenidas en esta clase, estudiarla nos va a llevar un buen rato. La clase Monobehaviour es uno de los miembros destacados del top ten de la API de Unity, así que es vital dominarla con una cierta solvencia. MonoBehaviour es la clase base de la que derivan todos los scripts. Ni más ni menos. Al ser clase base, salvo en C# no hace falta escribirla expresamente cuando llamemos a alguna de sus funciones, pues Unity la da por sobreentendida. Dicho de otra manera, para Unity es lo mismo esto
MonoBehaviour.Invoke()
que esto
Invoke()
Vamos ya a empezar con las funciones de MonoBehaviour:
FUNCIONES:
Invoke: function Invoke (methodName : String, time : float) : void
Llama a la función que le pasemos como primer parámetro en el tiempo contado en segundos que le indiquemos como segundo parámetro. Aunque creo que el funcionamiento de esta función es bastante evidente, nada mejor que verlo con un ejemplo. Y previamente a empezar a diseñar el mismo, vamos a reorganizar nuestra escena en la interfaz de Unity siguiendo estos pasos: 1.- Eliminamos el Gameobject Cube. 2.- Ubicamos el Gameobject Cubo en -2,0,0. 3.- Eliminamos el script vinculado a la esfera. 4.- Colocamos la esfera en 2,0,0. 5.- Asignamos por la vía de arrastre MiPrimerScript en Project al GameObject PortaScripts. Si por lo que sea no has seguido todas las lecciones y en la jerarquía no tienes un gameobject con ese nombre, crea un Gameobject vacío y lo bautizas como PortaScripts. 6.- Doble click en MiPrimerScript para abrir el editor y poder trabajar en él. Tecleamos esto:
var original : GameObject; function OtroDeLoMismo() { Instantiate(original, original.transform.position + Vector3.right, original.transform.rotation); } Invoke("OtroDeLoMismo", 5);
Seleccionamos PortaScripts y en el inspector arrastramos el cubo hasta el valor de la variable Original. Pulsamos el play. Transcurridos cinco segundos, observaremos que la función Invoke llama a la función OtroDeLoMismo, que a su vez crea una instancia clonada de nuestro cubo una unidad a la derecha. Si queremos, podemos arrastrar la esfera a la variable Original, para que sea ella la clonada. Un problema que me he encontrado a la hora de hacer este ejemplo, es que no sé si Invoke funciona para llamar a una función que tenga uno o más parámetros, y en caso afirmativo cómo se ha de añadir dicho parámetro al string. Si alguien conoce la respuesta a esto agradecería lo reportara. Bueno, y como entrante por hoy ya tenemos bastante (yo al menos).
42. CLASE MONOBEHAVIOUR (II)
InvokeRepeating: function InvokeRepeating (methodName : String, time : float, repeatRate : float) : void
Invoca la función que le pasamos como string para dentro del número de segundos que le pasamos como segundo parámetro, y una vez se ejecuta, lo vuelve a hacer cada repeatRate segundos (el tercer parámetro. Veremos claramente su funcionamiento rehaciendo ligeramente el ejemplo anterior (previamente es mejor que coloquemos la esfera en position.Z = 3):
var original : GameObject; function OtroDeLoMismo() { var copia : GameObject = Instantiate(original, original.transform.position + Vector3.right, original.transform.rotation); copia.transform.position += Vector3(Time.time - 5, 0,0); } InvokeRepeating("OtroDeLoMismo", 5,1);
Arrastramos nuestro cubo para inicializar la variable "original" en el inspector, y le damos al play. Nuestro cubo, pasados los 5 primeros segundos, empezará a clonarse cada segundo. Vamos por partes: Lo que hemos hecho es primero declarar una función que -al igual que en el ejemplo anterior- clona el objeto que le hayamos arrastrado a "original" y lo coloca una unidad a la derecha del objeto clonado. Asimismo, para que no se nos amontonen los cubos en el mismo punto, aprovechamos que la función Instantiate devuelve el objeto clonado, para almacenarlo en una variable -que llamamos copia- y lo que hacemos es que cada nuevo objeto instanciado se mueva una unidad más a la derecha que el anterior, cosa que obtenemos aprovechando que Time.time hace precisamente eso, avanzar una unidad cada segundo. Al resultado le restamos 5, que es el número de segundos que transcurren hasta que empiezan a clonarse los cubos, por obra y gracia del segundo parámetro de InvokeRepeating. Si no le restáramos ese 5, el primer muro se clonaría 5 unidades a la derecha del cubo clonado. Observamos que, centrándonos en la función estudiada, que InvokeRepeating llama a la función transcurridos 5 segundos -en este caso- y pasados los mismos, se repite de forma infinita cada segundo (3er parámetro).
CancelInvoke: function CancelInvoke () : void function CancelInvoke (methodName : String) : void
Tal como seguro que estaréis suponiendo, CancelInvoke cancela las funciones que hayan sido invocadas por las dos funciones anteriores para un determinado script. La primera variación de esta función no tiene parámetros, y al llamarse cancela todas las funciones Invoke e InvokeRepeating del script. La segunda variación de la función sólo cancelará la función de dicho tipo que coincida con el nombre que se le pasa como parámetro. Arreglemos un poco más nuestro script dándole al usuario la posibilidad de cancelar la clonación de cubos.
var original : GameObject; function OtroDeLoMismo() { var copia : GameObject = Instantiate(original, original.transform.position + Vector3.right, original.transform.rotation); copia.transform.position += Vector3(Time.time - 5, 0,0); } InvokeRepeating("OtroDeLoMismo", 5,1); function Update() { if (Input.GetButton ("Fire1")) CancelInvoke(); }
Es la misma función anterior, sólo que con el añadido que le hemos puesto, la función update comprobará cada frame si el usuario ha pulsado la tecla definida en input como Fire1. Si tenemos la configuración por defecto, debería ser el ctrl izquierdo o el botón izquierdo del ratón.
IsInvoking: function IsInvoking (methodName : String) : boolean
Este primer prototipo de la función devuelve true si hay algún invoke pendiente de la función que se introduce como parámetro.
function IsInvoking () : boolean
En esta segunda modalidad devuelve true si algún invoke pendiente en este script.
43. CLASE MONOBEHAVIOUR (III)
StartCoroutine: function StartCoroutine (routine : IEnumerator) : Coroutine
Antes de entrar a analizar esta función quisiera explicar aunque fuera de forma superficial lo que es una corutina y cómo son tratadas por Unity.
Una corutina es una función -que individualizamos con la palabra clave yield- que puede suspender su ejecución hasta que una instruction dada -que también contendrá la palabra clave yield- termine. Veámoslo con un ejemplo sacado del manual de referencia de Unity:
//Imprime Starting 0.0 print ("Starting " + Time.time); /* Se llama a continuación a la función WaitAndPrint como coroutine, cosa que se consigue anteponiendo la palabra clave yield. Al hacer esto permitimos que la función se pueda suspender por una instrucción yield. Por lo tanto, el flujo del script se va a la función waitandprint, donde se encuentra con una instrucción yield que le dice que espere cinco segundos, luego imprimirá “WaitandPrint 5.0 (que será el tiempo transcurrido desde el inicio del cómputo) y volverá arriba para imprimir “done 5.0” (o un número cercano).*/ yield WaitAndPrint(); print ("Done " + Time.time); function WaitAndPrint () { // suspende la ejecución por 5 segundos yield WaitForSeconds (5); print ("WaitAndPrint "+ Time.time); }
Si no veis clara la diferencia con el proceso secuencial habitual, no tenéis más que borrar la palabra yield que está antes de la llamada a la función. Si hacéis eso, el programa empezará escribiendo "starting 0", bajará a la siguiente instrucción, donde se encuentra con la función WaitAndPrint, que le indica que no va a hacer nada durante cinco segundos, así que pasará inmediatamente a la siguiente declaración e imprimirá "Done 0", para luego, cinco segundos después, imprimir por fin "Waitandprint 5".
Bien. Con estas nociones vamos ya a la función que nos ocupa, StartCoroutine. Como su nombre indica, inicia una corrutina cuya ejecución puede ser pausada en cualquier momento usando una instrucción yield. Programando en javascript, no es necesario escribir StartCoroutine, ya que el compilador la dará por supuesta. Veamos un segundo ejemplo sacado igualmente del manual de referencia de Unity. En este ejemplo, a diferencia del anterior, vamos a invocar una corrutina que no suspenderá la ejecución de la función que se desarrolla en paralelo:
function Start() { // - Después de 0 segundos imprimirá "Starting 0.0" // - Después de 0 segundos, imprimirá "Before WaitAndPrint Finishes 0.0" // - Después de 2 segundos imprimirá "WaitAndPrint 2.0" //Empezamos (función Start) imprimiendo “Starting 0.0” print ("Starting " + Time.time); /* Ahora vamos a iniciar la función WaitAndPrint como coroutine (en paralelo), pero sin suspender el flujo de llamadas a funciones que se puedan seguir dando en el hilo principal. Si hubiéramos querido suspenderlo, hubiéramos utilizado la fórmula
del ejemplo anterior, y hubiéramos antepuesto la palabra clave yield a la llamada a la función. Lo que sigue podría también haberse escrito (en javascript) así: WaitAndPrint(2.0) y el compilador añadiría el StartCoroutine por ti automáticamente.*/ StartCoroutine(WaitAndPrint(2.0)); /*Como no hemos interrumpido el flujo principal con yield, la declaración que viene ahora se ejecutará antes que la la función anterior, puesto que la anterior ha de esperar dos segundos y el print que viene ahora no.*/ print ("Before WaitAndPrint Finishes " + Time.time); } function WaitAndPrint (waitTime : float) { // suspende la ejecución por los segundos que le fijemos en waitTime yield WaitForSeconds (waitTime); print ("WaitAndPrint "+ Time.time); }
La función StartCoroutine tiene una segunda modalidad: function StartCoroutine (methodName : String, value : object = null) : Coroutine
Es como la función anterior, pero usando un string. Esta variante tiene una mayor sobrecarga de procesamiento, pero es la única que se puede detener con StopCoroutine. Vamos a realizar otro ejemplo en el que , aparte de usar la variante con string de la función, aprovecharemos para ilustrar de nuevo otra posibilidad que nos dan este tipo de instrucciones Yield. El script ejecuta la función DoSomething, que dispara un bucle. Al siguiente frame el script se detendrá un segundo al toparse con la instrucción yield, pasará al yielf waitforseconds, esperará un segundo y luego se detendrá.
function Start () { StartCoroutine("DoSomething", 2.0); yield WaitForSeconds(1); StopCoroutine("DoSomething"); } function DoSomething (someParameter : float) { while (true) { print("DoSomething Loop"); // Detiene la ejecución de esta corutina y vuelve al loop principal hasta el //siguiente frame. yield; } }
StopCoroutine: function StopCoroutine (methodName : String) : void
Como hemos visto en el ejemplo anterior, detiene las corrutinas con el nombre que le suministramos como parámetro iniciadas en este script. Dicho nombre de función lo hemos de pasar como un string, y no hemos de olvidar que sólo las corrutinas con el nombre indicado que se hayan iniciado mediante la variante string de StartCoroutine (la segunda modalidad de las anteriormente vistas) podrán ser detenidas con StopCoroutine.
StopAllCoroutines: function StopAllCoroutines () : void
Detiene todas las corrutinas que estén corriendo en ese script.
44. CLASE MONOBEHAVIOUR (IV)
El resto de funciones que nos quedan por estudiar de la clase MonoBehaviour (que son unas cuantas) son comúnmente conocidas como "funciones sobreescribibles". El nombre les viene dado porque estas funciones tienen, con respecto a las "standard", la peculiaridad de que nos permiten diseñar nosotros su contenido. Por explicarlo de alguna manera, la firma de estas funciones, que es lo que nos brinda Unity, supondrían el cuándo y lo que nosotros escribiremos será el qué y el cómo. Incidentalmente, y para entender un poco mas la pléyade de funciones que vienen a continuación, vamos a explicar el orden en que Unity inicializa los distintos elementos que lo componen cada vez que se carga una escena en el juego: -Primero se cargan los objects (game objects y components). -Acto seguido se cargan los scripts que van vinculados a estos objects, y una serie de funciones (las que veremos a continuación) son llamadas en un orden específico: 1.- Awake. 2.- Start. 3.-Update/FixedUpdate. 4.-LateUpdate.
Update:
function Update () : void
Esta función, con la que ya hemos lidiado en alguna ocasión, es llamada cada frame, si el Monobehaviour (script que la invoca) está activo. Es la función más usada en los scripts, si bien arrastra el inconveniente de que cada ordenador puede tener un framerate distinto, por lo que la misma instrucción de movimiento, por ejemplo, daría pie a diferentes velocidades dependiendo del framerate de cada uno (con resultados catastróficos, sin ir más lejos, en juegos online para varios jugadores). Para transformar las unidades de actualización de la función de frames a segundos se utiliza Time.deltaTime.
LateUpdate: function LateUpdate () : void
LateUpdate es llamado una vez todas las funciones Update han sido llamadas. Esto nos permite ordenar la ejecución de scripts. Por ejemplo, una cámara que sigue a un objeto debería implementarse en un lateUpdate, pues cabe que el objeto al que sigue se inicialice con un determinado movimiento en update, movimiento que debería tener en cuenta la cámara.
FixedUpdate: function FixedUpdate () : void
Esta función se ejecuta cada cierto número preestablecido y fijo de frames, lo que hace que no presente los problemas de update. Se debe utilizar en aquellos scripts que impliquen un componente de físicas, y sobre todo, siempre se ha de utilizar cuando haya que añadir una fuerza a un Rigidbody.
Awake: function Awake () : void
Awake es llamada cuando se inicia el script, osea, cuando se carga la escena, y justo después de que se carguen los objects (gameobjects y components) a que el script hace referencia. De tal manera, es útil para inicializar variables o estados del juego antes de que el juego empiece, referenciando si es preciso a los objects a que hacen mención (y que ya habrán sido como decimos inicializados previamente). Por ejemplo, dentro de un awake puedes tranquilamente usar Gameobject.FindWithTag. Cada función awake para gameobjects es llamada de forma aleatoria, razón por la cual se utiliza Awake para colocar referencias entre scripts (pej, el script X llamará al script Y) pero se ha de utiliza Start para pasar información de un lado a otro (pej: El script x le pasa el valor 5 al script Y), ya que Awake es llamada siempre antes que Start y así al mandar valores vía Start nos aseguramos de que las relaciones entre scripts estén ya establecidas vía Awake. Por último, indicar que Awake no puede formar parte de una corrutina, y que será llamada aunque en el inspector la instancia del script esté deshabilitada/desmarcada.
Start: function Start () : void
Es llamada después de Awake y antes de Update. Al igual que awake, sólo es llamada una vez a lo largo de toda la vida del script. Aparte del momento en que son llamadas, Start se diferencia de Awake en que Start sólo es llamada si la instancia del script está habilitada, esto es, tiene su checkbox marcado en el inspector. Así, cuando un gameobject es inicialmente usado en una escena esta función es llamada automáticamente.
Reset: function Reset () : void
Resetea a los valores por defecto, restaurando los valores originales. Esta función es llamada cuando se pulsa reset en el inspector o cuando se añade un componente por primera vez.
45. CLASE MONOBEHAVIOUR (V)
OnMouseEnter: function OnMouseEnter () : void
Esta función es llamada cuando el cursor entra en un collider o un GUIElement. Veámoslo con un ejemplo muy sencillo. Tecleamos:
function OnMouseOver(){ Debug.Log("El mouse está sobre el objeto " + gameObject.name); }
Arrastramos el script al cubo. Pulsamos play y observaremos que cuando el cursor está sobre el cubo, se imprime la frase debajo de la ventana game.
Esta función no será llamada en objetos que pertenezcan a Ignore Raycast layer. OnMouseEnter puede ser una corrutina, siempre que utilicemos una instrucción Yield en la función, y el evento será enviado a todos los scripts vinculados con el collider o GUIElement
OnMouseOver: function OnMouseOver () : void
Esta función es llamada cada frame en la que el mouse permanezca sobre el Collider o GUIElement, a diferencia de onMouseEnter, que sólo se dispara cuando entra el mouse. Como esta función se actualiza cada frame, podemos hacer cosas como la del ejemplo:
function OnMouseOver () { renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime; }
Si pulsamos play y mantenemos el cursor sobre el cubo, lentamente éste se irá tornando de color rojizo, debido al paulatino aumento del componente R (red) de su RGB. OnMouseOver puede ser, como OnMouseEnter, una corrutina, simplemente utilizando la declaración yield en la función
OnMouseExit: function OnMouseExit () : void
Esta función es llamada cuando el ratón ya no esta sobre un GUIElement o un Collider. Podemos completar con ella el script anterior:
function OnMouseOver () { renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime; } function OnMouseExit () { renderer.material.color = Color.white; }
Cuando se retira el mouse del cubo, éste retorna a su color blanco inicial. Como las precedentes, la función no es llamada en objetos que tienen el Ignore Raycast Layer y puede formar parte de una coroutine.
OnMouseDown: function OnMouseDown () : void
Es llamada cuando el usuario pulsa el botón del mouse sobre un GUIElement o Collider. Por seguir con el ejemplo anterior, podríamos añadir al script:
function OnMouseOver () { renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime; } function OnMouseExit () { renderer.material.color = Color.white; } function OnMouseDown () { renderer.material.color = Color.blue; }
Al presionar el botón del ratón, el cubo se torna de color azul. A esta función le es de aplicación lo indicado para las precedentes en relación con Ignore Raycast y la posibilidad de ser una corrutina.
46. CLASE MONOBEHAVIOUR (VI)
Seguimos con las funciones sobreescribibles. OnMouseUp: function OnMouseUp () : void
Esta función es llamada cuando el usuario libera/suelta el botón del ratón. Es llamada incluso si el mouse no está al soltar el botón sobre el mismo GUIElement o Collider en que estaba cuando el botón fue presionado. (Para que tuviera ese comportamiento habría que usar OnMouseUpAsButton, que vamos a examinar a continuación)
OnMouseUpAsButton: function OnMouseUpAsButton () : void
Como anticipábamos en la función anterior, ésta es llamada sólo cuando el mouse es liberado estando sobre el mismo GUIElement o Collider en el que fue presionado.
OnMouseDrag: function OnMouseDrag () : void
Es llamada esta función cuando el usuario presiona el botón del mouse sobre un GUIElement o un Collider y todavía lo mantiene presionado. Es llamada cada frame mientras el botón siga presionado.
OnTriggerEnter, OnTriggerExit, OnTriggerStay, OnCollisionEnter, On CollisionExit, OnCollisionStay:
Todas estas funciones ya fueron explicadas en la clase Collider, así que a la misma me remito.
OnControllerColliderHit: function OnControllerColliderHit (hit : ControllerColliderHit) : void
Es llamada cuando nuestro character controller golpea un collider mientras realiza un movimiento, de tal manera que muchas veces esta función sirve para empujar objetos cuando colisionan con el personaje. Hagamos unas modificaciones previas al pertinente ejemplo en la interfaz de Unity. 1.- Eliminamos el script vinculado a PortaScripts, para que no nos lance errores. 2.- Añadimos un CharacterController al cubo, reemplazando cuando nos lo pida Unity el antiguo boxCollider. 3.- Desconectamos provisionalmente el boxCollider del gameobject Suelo, dado que en caso contrario será el suelo el primer collider con el que se tope nuestra función.
4.- Escribimos este script, que le vincularemos al cubo:
var miCharCon : CharacterController; miCharCon = GetComponent(CharacterController); function Update() { miCharCon.Move(Vector3(1 * Time.deltaTime ,0,0)); } function OnControllerColliderHit(teToco) { teToco.rigidbody.AddForce(Vector3(0,0,50)); }
Play. Nuestro character controller (también conocido como cubo) avanza hacia la izquierda a razón de un metro por segundo, y cuando se topa con un controller (la esfera), algunos datos de la colisión y sus intervinientes son pasador al único parámetro de la función, que es de la clase ControllerColliderHit. La clase ControllerColliderHit, aunque la veremos más en profundidad en otra lección, cuenta con una serie de variables, entre las que destacan:
controller: El character controller que golpea el collider (nuestro cubo en el collider: esfera, rigidbody: si es
ejemplo) El collider que es golpeado por el character controller (la aquí) El rigidbody que ha sido golpeado por el character controller, que el collider que golpeamos tiene rigidbody (en el ejemplo lo
usamos gameObject: transform: point: normal: coordenadas
porque la esfera tiene rigidbody). El game object que ha sido golpeado por el character controller. El transform que ha sido golpeado por el controller. El punto de impacto en coordenadas globales. El normal de la superficie que ha sido colisionado en
globales. moveDirection: Aproximadamente la dirección desde el centro de la cápsula del character controller al punto que tocamos. moveLength: La distancia que el character controller ha recorrido hasta golpear con el collider.
Entonces, volviendo al ejemplo, a través del parámetro de tipo ControllerColliderHit que hemos dado en llamar teToco, accedemos a la variable rigidbody, que obviamente se corresponde con el rigidbody de la esfera, y a partir de ahí le asignamos un comportamiento, que en este caso es meramente desplazarse en el eje Z.
OnJointBreak: function OnJointBreak (breakForce : float) : void Esta función ya la explicamos en la lección correspondiente a la clase Joint.
OnParticleCollision:
function OnParticleCollision (other : GameObject) : void
Esta función es llamada cuando una partícula choca con un collider. Puede por tanto usarse para computar el daño recibido por un gameobject cuando choca con partículas. Este mensaje es enviado a todos los scripts vinculados con el WorldParticleCollider y al collider que fue colisionado. El mensaje es sólo enviado si habilitamos SendCollisionMessage en el inspector del WroldParticleCollider. Quedémonos de momento con esta sucinta información, que ya trabajaremos cuando nos toque lidiar con la clase ParticleEmitter.
47. CLASE MONOBEHAVIOUR (VII)
OnBecameVisible: function OnBecameVisible () : void
Se llama a esta función cuando el renderer se convierte en visible por alguna cámara. Este mensaje se envía a todos los scripts relacionados con el renderer. Esta función y su opuesta –OnBecameInvisibleson útiles para evitar cómputos que son sólo necesarios cuando el objeto es visible.
OnBecameInvisible: function OnBecameInvisible () : void
Es llamada cuando el renderer ya no es visible por ninguna cámara.
OnLevelWasLoaded: function OnLevelWasLoaded (level : int) : void
Esta función es llamada después de que un nuevo level ha sido cargado. El parámetro level de la función es el level que ha sido cargado. Si quiere ver el índice de levels de su juego, use el menú File=>Build settings.
OnEnable: function OnEnable () : void
Es llamada cuando el objeto pasa a estar habilitado y activo.
OnDisable: function OnDisable () : void
Es llamada cuando el objeto se convierte en deshabilitado o inactivo. También cuando el objeto es destruido y puede ser usada la función para procesos de limpieza. Cuando los scripts son recargados después de que la compilación haya acabado, OnDisable se llamará seguida por OnEnable después de que el script haya sido cargado.
OnDestroy: function OnDestroy () : void
Es llamada cuando el MonoBehaviour (script) es destruido. Sólo puede ser llamada para gameobjects que previamente hayan estado activos.
OnPreCull: function OnPreCull () : void
Es llamada antes de que la cámara deseche la escena. Culling es un proceso que determina qué objetos son visibles en la cámara, y OnPreCull es llamada justo antes de dicho proceso. Esta function es llamada sólo si el script está vinculado con la cámara y está activado. Si queremos cambiar los parámetros de visión de la cámara (tal como fieldOfView) lo tendremos que hacer aquí. La visibilidad de los objetos de la escena se determinará en base a los parámetros de la cámara después de la función OnPreCull.
OnPreRender: function OnPreRender () : void
Es llamada antes de que la cámara empiece a renderizar la escena. Sólo se llama si el script está vinculado a la cámara y activo. Es importante comprender que si cambiamos con esta función los parámetros de visión de la cámara, como por ejemplo fieldOfView, sólo tendrán efecto el siguiente frame, por lo que hay que hacerlo mejor en la función OnPreCull, como hemos dicho antes.
OnPostRender: function OnPostRender () : void
Es llamada después de que la cámara acaba de renderizar la escena, siempre que el script esté vinculado con la cámara y activo.
OnRenderObject: function OnRenderObject () : void
Es llamada después de que la cámara renderiza la escena. Se diferencia de OnPostRender en que OnRenderObject es llamada para cada objeto que tenga un script con la función, sin importar si está vinculado a una cámara o no.
OnWillRenderObject: function OnWillRenderObject () : void
Esta función es llamada una vez por cada cámara si el objeto es visible. Es llamada durante el proceso de culling (que podríamos traducir por elección o desechamiento), justo antes de renderizar todos los objetos seleccionados. Podría usarse por tanto esta función para crear texturas que pudieran actualizarse sólo si el objeto a renderizar es en realidad visible.
48. CLASE MONOBEHAVIOUR (VIII)
OnGUI: function OnGUI () : void
Es llamada para renderizar y manejar eventos GUI. Esto significa que nuestra implementación de OnGUI podría ser llamada varias veces por frame, a razón de una llamada por evento. Esto lo trabajaremos más cuando estudiemos las clases Event y GUI.
OnRenderImage: function OnRenderImage (source : RenderTexture, destination : RenderTexture) : void
Es llamada cuando se tiene toda la información de renderizado de una imagen, permitiendo modificar la imagen final procesándola con filtros. La imagen entrante (source) es de tipo renderTexture y el resultado lo almacenamos en otro parámetro del mismo tipo (destination en el prototipo de la función). Cuando hay múltiples filtros de imagen vinculados a la cámara, estos procesan la imagen secuencialmente, pasando el primer filter destination como source del siguiente filtro. Este mensaje será enviado a todos los scripts vinculados a la cámara.
OnDrawGizmosSelected: function OnDrawGizmosSelected () : void
Implementa esta function si quieres dibujar gizmos sólo si el objeto está seleccionado. Vamos a verlo con un ejemplo simple. Es necesario para ver el efecto que previo a darle al play el cubo no esté seleccionado.
function OnDrawGizmosSelected () { Gizmos.color = Color.white; Gizmos.DrawCube (transform.position, Vector3 (2,2,2)); }
No nos fijemos demasiado en los elementos de la clase Gizmos, que ya veremos en su momento. De momento sólo necesitamos saber que , cuando se seleccione el cubo -bien en la jerarquía, bien en la escena, bien con el juego activado o incluso sin que el juego esté activado- nos aparecerá un gizmo que se corresponderá a un cubo blanco de 2 unidades de lado.
Probémoslo.
OnDrawGizmos: function OnDrawGizmos () : void
Implementa esta función si quieres dibujar gizmos que aparezcan siempre dibujados. Esto te permite rápidamente seleccionar objetos importantes en tu escena, por ejemplo. Notad que esta función usa una posición del ratón que es relativa a la vista de la escena. Para mostrar esto en un ejemplo necesitamos unos pasos previos:
1.- Buscad por la red la imagen de una bombilla (no demasiado grande). 2.- La guardáis y le asignáis el nombre "Bombilla" (Fijaros en la extensión de la imagen. Si es distinta de la que voy a utilizar yo, cambiadla en el script) 3.- Buscáis la carpeta de nuestro proyecto Unity, y arrastráis la imagen dentro de la carpeta assets. 4.- En la interfaz, hacemos click derecho sobre Proyecto =>Create =>Folder. Llamamos a la nueva carpeta Gizmos (es donde por defecto buscará Unity) 5.- En Proyecto, arrastramos la imagen dentro de esta carpeta. 6.- Doble click sobre MiPrimerScript. Tecleamos:
function Update(){ transform.Translate(-Vector3.right * Time.deltaTime); } function OnDrawGizmos() { Gizmos.DrawIcon (transform.position + Vector3(0,5,0) , "Bombilla.jpeg"); } Si hemos seguido correctamente estos pasos, cubo e imagen deberían desplazarse al unísono hacia la izquierda, tal que así:
49. CLASE MONOBEHAVIOUR (y IX)
OnApplicationPause: function OnApplicationPause (pause : boolean) : void
Es enviada a todos los gameobjects cuando el jugador presiona la pausa.
OnApplicationFocus: function OnApplicationFocus (focus : boolean) : void
Función enviada a todos los gameobjects cuando el jugador obtiene o pierde el foco.
OnApplicationQuit: function OnApplicationQuit () : void
Función que se envía a todos los gameobjects antes de que la aplicación se cierre. En el editor es llamada cuando el usuario detiene el play, en la web es llamada cuando la página se cierra.
OnPlayerConnected: function OnPlayerConnected (player : NetworkPlayer) : void
Es llamada en el servidor cada vez que un nuevo jugador se conecta con éxito. Esta función y las que relacionaremos a continuación las trataremos en profundidad la función Network y familia.
OnServerInitialized: function OnServerInitialized () : void
Llamada en el server cada vez que Network.InitializeServer es invocada y completada.
OnConnectedToServer: function OnConnectedToServer () : void
Es llamada esta función en el cliente cuando consigues conectarte con éxito al servidor.
OnPlayerDisconnected: function OnPlayerDisconnected (player : NetworkPlayer) : void
Llamada en el server cada vez que un jugador se desconecta del server.
OnDisconnectedFromServer: function OnDisconnectedFromServer (mode : NetworkDisconnection) : void
Llamada en el cliente cuando la conexión se pierde o desconectas del servidor.
OnFailedToConnect: function OnFailedToConnect (error : NetworkConnectionError) : void
Llamada en el cliente cuando un intento de conexión falla por alguna razón. La razón por la que falla es pasada como una enumeración de tipo Network.ConnectionError.
OnFailedToConnectToMasterServer: function OnFailedToConnectToMasterServer (error : NetworkConnectionError) : void
Llamada en clientes o servidores cuando hay un problema conectando con el MasterServer. La razón del error es pasada como una enumeración de tipo Network.ConnectionError.
OnMasterServerEvent: function OnMasterServerEvent (msEvent : MasterServerEvent) : void
Llamada en clientes o servidores cuando informan de eventos desde el MasterServer, como por ejemplo que haya tenido éxito el registro en el host. El tipo MasterServerEvent es una enum con los siguientes valores:
RegistrationFailedGameName: El registro falló porque se indicó un nombre de juego vacío. RegistrationFailedGameType: El registro falló porque se indicó un tipo vacío de juego. RegistrationFailedNoServer: El registro falló porque ningún servidor está funcionando. RegistrationSucceeded: El registro al servidor maestro tuvo éxito, y se recibió confirmación. HostListReceived: Recibida una lista de hosts desde el master server.
OnNetworkInstantiate: Function OnNetworkInstantiate (info : NetworkMessageInfo) : void Llamada en objetos que han sido instanciados en red con Network.Instantiate. Esto es útil para deshabilitar o habilitar componentes de objetos los cuales han sido instanciados y su comportamiento depende de si ellos son de propiedad local o remota. OnSerializeNetworkView: function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo) : void
Usada para personalizar la sincronización de variables en un script controlado por una network view. Esto es automáticamente determinado si las variables que han sido serializadas deben ser enviadas o recibidas.
50. CLASE CAMERA (I)
Bienvenid@s a otra de las clases importantes de Unity. La clase cámera le permite al jugador interactuar con tu juego. La cámara en Unity es un dispositivo a través del cual el jugador ve el mundo. Hemos de diferenciar desde el primer momento, y ahí residen no pocas confusiones de los recién llegados a Unity, entre pantalla y cámara. Ambas son cosas diferentes y tienen una unidad de medida distinta: La unidad de medida de la pantalla está definida en píxeles. La esquina izquierda-inferior de la pantalla es (0,0), la derecha-superior está contenida en las variables (pixelWidth,pixelHeight), que representan la anchura y altura de la pantalla respectivamente. La posición Z estaría en unidades globales contadas desde la cámara. Si la unidad de medida es relativa a la cámara, la esquina izquierda-inferior de la cámara es (0,0) y la derecha-superior (1,1). La posición Z estaría en unidades globales desde la cámara.
Con esto en mente, empecemos:
VARIABLES: fieldOfView: var fieldOfView : float
Variable que contiene el campo de visión de la cámara en grados. Se referiría al campo de visión vertical, ya que el horizontal varía dependiendo del viewport’s aspect ratio. El campo de visión es ignorado cuando la cámara está en modo ortográfico. Antes de empezar con los ejemplos, vamos a retocar un par de cosillas en la interfaz de Unity. En primer lugar, en Proyecto le damos al botón derecho del ratón sobre la carpeta Gizmos=>Delete, para deshacernos de la bombilla. Nos aseguramos de que el cubo está en posición -2,0,0. Eliminamos el script vinculado al cubo. Nos aseguramos de que la cámara no esté en modo ortográfico, sino en perspectiva. Doble click en MiPrimer Script:
Camera.main.fieldOfView = 20;
Arrastramos el script a PortaScripts en la Jerarquía. Notemos antes que nada que en el script nos estamos refiriendo a la cámara principal. Si quisiéramos que el script afectara a otra cámara distinta, por un lado deberíamos quitarle el "main" del script y por otro lo tendríamos que arrastrar a la cámara que queremos afectar por éste. Vamos a probarlo, para que no haya dudas, pero antes dadle al play y comprobar cómo el campo de visión, que por defecto es 60, pasa a ser 20 y -aunque parezca al principio poco intuitivo- al tener un ángulo menor de visión, dicho campo reducido es ampliado para que ocupe toda la pantalla. Esto a muchos les recordará las fotos en las bodas: cuanta más gente queremos abarcar (más campo de visión) más lejos tenemos que tirar la foto. Pero habíamos prometido que aplicaríamos el ejemplo a una cámara no principal, así que ahí vamos. Gameobject=>Create other=>Camera. Llamamos a la Camera "Camara 2". Ahora la ubicaremos en la siguiente posición/rotación: Posicion:-0.6,15,1 Rotacion:85,0,0 Con la cámara 2 seleccionada, le damos al play y observamos nuestro pequeño mundo desde otra perspectiva. Nos daremos cuenta de que Unity nos lanza un mensaje, avisándonos de que tenemos dos receptores (listeners) de audio en la escena. Esto es porque por defecto cada cámara tiene un Audio Listener para recoger el sonido además de la imagen. Para evitar problemas, eliminamos el AudioListener de la cámara 2 en el inspector. Vale, y ahora montemos otro miniscript para la segunda cámara. Hacemos doble click en MiSegundoScript (ya que el primero lo tenemos ocupado) y tecleamos:
camera.fieldOfView = 20;
Arrastramos este script a la Camara 2 (si lo arrastráramos al PortaScripts no funcionaría, ya que no sabría a qué cámara ha de afectar, salvo que creáramos una variable "expuesta" de tipo Camera a la que arrastrar esta y etc, etc, aunque para lo que queremos hacer es más cómodo el método primero) Le damos al play y observaremos que funciona. Aprovecho para comentaros una cosilla que seguro que la mayoría ya sabe. Cada cámara al ser creada tiene una variable (depth, que luego veremos) que establece la prioridad de cada una. Cuanto más alto el valor de depth, más prioridad tiene la cámara. Si os fijáis en el inspector, la cámara principal debería tener la variable depth en -1, y camara 2 en 0. Es por ello que cuando le damos al play, la vista que nos aparece es la que recoge la cámara 2. Si quisiéramos retomar a vista grabada por la cámara principal, no tendríamos más que darle un valor a depth superior al del resto de cámaras.
51. CLASE CAMERA (II)
nearClipPlane: var nearClipPlane : float
El plano de recorte de cerca. Cualquier cosa que se halle más cerca de la cámara de la distancia establecida en esta variable no se mostrará en la cámara. Veámoslo con un ejemplo un poco extremo. Si a MiSegundoScript lo dejamos como sigue...
camera.nearClipPlane = 13;
... observaremos que todo lo que se halle a menos de 13 metros de la cámara será recortado.
farClipPlane: var farClipPlane : float
El plano de recorte de lejos. Cualquier cosa que se halle más lejos de la cámara de la distancia establecida en esta variable no se mostrará en la cámara. Probad cambiar en nuestro ejemplo el "near" por el "far", para obtener una especie de negativo de la toma anterior.
renderingPath: var renderingPath : RenderingPath
Indica el tipo de renderizado de entre los que contempla la enum RenderingPath (UsePlayerSettings, VertexLit, Forward y DeferredLighting). Desde el inspector, con la cámara en uso seleccionada, podemos intercambiar entre las cuatro posibilidades para captar sus matices.
actualRenderingPath: var actualRenderingPath : RenderingPath
Variable de sólo lectura que contiene el rendering path que se está usando.
orthographicSize: var orthographicSize : float
El campo de visión de la cámara cuando está en modo ortográfico. En el próximo ejemplo colocamos a la camara 2 primero en modo ortográfico, y le fijamos luego un tamaño de 3, de tal forma que al tener poco
campo de visión amplíe los objetos enfocados.
camera.orthographic = true; camera.orthographicSize = 3;
Podríamos hacer lo mismo con la cámara principal modificando MiPrimerScript y añadiendo "Main". Recordemos que con la cámara principal no es preciso que el script se incluya en el gameobject mainCamera.
Camera.main.orthographic = true; Camera.main.orthographicSize = 3;
orthographic: var orthographic : boolean
Variable de tipo booleano que indica si la cámara está en modo ortográfico (true) o en perspectiva (false) y que permite pasar de uno a otro. Como ya hemos visto, cuando esta variable esta en true, el campo de visión de la cámara se define por orthographicSize, y cuando está en false por fieldOfView.
depth: Var depth : float
La profundidad de la cámara en el orden de renderizado de las cámaras. Las cámaras con profundidad más baja son renderizadas antes de las cámaras con profundidad más alta. Si tienes varias cámaras, usa este control para decidir el orden en el que las cámaras mostrarán la escena si algunas de ellas no cubren la totalidad de la pantalla.
aspect: var aspect : float
Variable en la que se guarda/coloca la proporción de aspecto (aspect ratio), que es el nombre con que se conoce a la anchura dividida por la altura de la cámara. Por defecto, el aspect ratio es calculado automáticamente tomando como base la el aspect ratio de la pantalla. Esto es así incluso si la cámara no está renderizando el área entera. Si modificas el aspect ratio de la cámara, el valor permanecerá hasta que llames a la función camera.ResetAspect(). cullingMask: var cullingMask : int Es usada para renderizar de manera selectiva partes de la escena. Si el layerMask del gameobject (que veremos a no mucho tardar) y el cullingMask de la cámara son cero, entonces el game object será invisible para esa cámara. El layerMask es parecido al sistema de capas de Blender, que brinda la posibilidad de arrastrar objetos a una capa diferente. Con esto se consigue, por ejemplo, que objetos pertenecientes a una fase un poco más avanzada del juego no sean mostrados hasta que permitamos a la cámara renderizar los relativos a esa capa. backgroundColor: var backgroundColor : Color
Variable que indica el color con el cual la pantalla será completada.
52. CLASE CAMERA (III)
rect:
var rect : Rect
Establece qué parte de la pantalla esta la cámara renderizando. Dicha parte de la pantalla es fijada a través de una instancia de la estructura Rect, en coordenadas normalizadas. Por coordenadas normalizadas nos referimos a que los valores en el rango del rect irán de 0,0 (izquierda,abajo) a 1,1 (derecha,arriba) Así, si queremos que la cámara a la que vinculamos el script renderice toda la pantalla, meramente haremos lo siguiente. Editamos MiSegundoScript (que es el que tenemos vinculado a Camara 2) y escribimos:
camera.rect = Rect (0, 0, 1, 1);
Tratándose de coordenadas normalizadas, lo que estamos pidiéndole aquí a Unity es que lo que esté captando la cámara 2 se nos vea en el rectángulo que le pasamos a la variable rect, y que en este caso ocupa toda la pantalla (de 0,0 a 1,1) Si queremos que lo que capta la cámara dos se nos vea en la esquina superior derecha de la pantalla, calcularíamos estas coordenadas:
0,5: Corresponde al eje horizontal del primer punto, donde 0 sería izquierda y 1 derecha. 0,5: Corresponde al eje vertical del primer punto, donde 0 sería abajo y 1 arriba. El primer punto de nuestro rectángulo, pues, estaría justo en el centro de la pantalla. 1: Corresponde al eje horizontal del segundo punto. 1: Y este es el eje vertical del segundo punto. El segundo punto del rectántulo, por lo tanto, está justo en el vértice derecho superior.
Así que podemos rectificar el script anterior, de la manera indicada:
camera.rect = Rect (0.5, 0.5, 1, 1);
De esta manera, lo que esté grabando cámara dos queda relegado al recuadro superior derecho y, para el resto de la pantalla, entra en acción la cámara principal, que ya vimos que por profundidad (depth) es la
siguiente en la lista de prioridad. Debería quedar algo parecido a esto:
Pero, ¿qué sucedería si no hubiera ninguna cámara más?. Probémoslo. Seleccionamos Main Camera y en el inspector desmarcamos el check box que aparece junto al componente camera. Volvemos a darle al play, y observaremos que la parte de pantalla no incluida en el rectángulo aparece en negro, tal como se muestra a continuación:
pixelRect: var pixelRect : Rect
Indica/establece qué parte de la pantalla esta la cámara renderizando, pero a diferencia de rect, no lo indica en coordenadas normalizadas, sino en píxeles. Por lo tanto, este script sería el equivalente al anterior:
camera.pixelRect = Rect (Screen.width/2, Screen.height/2, Screen.width, Screen.height);
pixelWidth: var pixelWidth : float
Indica la anchura de la cámara en píxeles. (Sólo lectura).
pixelHeight: var pixelHeight : float
Y esta variable indica la altura de la cámara en píxeles(sólo lectura)
velocity: var velocity : Vector3
Variable de sólo lectura que indica la velocidad de la cámara en el espacio global.
53. CLASE CAMERA (IV)
clearFlags: var clearFlags : CameraClearFlags
Indica cómo la cámara completa el background. Admite los valores CameraClearFlags.Skybox (rellena el bacground con el skybox habilitado), CameraClearFlags.SolidColor (con el color que le indiquemos), CameraClearFlags.Depth (que mantiene el color que tuviera el background el frame anterior o en cualquier estado previo)o CameraClearFlags.Nothing (no se renderiza background alguno).
FUNCIONES:
ResetAspect: function ResetAspect () : void
Revierte el aspect ratio de la cámara al aspect ratio de la pantalla, acabando con el efecto de modificar la variable aspect.
Lo veremos mejor con un ejemplo (por cierto, si al término del ejemplo anterior no lo hicísteis, aprovechad ahora para volver a marcar el checkbox del componente Camera de Main camera) Editamos MiSegundoScript para que luzca como sigue:
camera.aspect = 2; yield WaitForSeconds (5); camera.ResetAspect();
Lo que estamos haciendo es lo siguiente: primero establecemos la variable aspect en 2. Recordemos que aspect es la anchura de la cámara dividida por la altura, esto es, en este caso la cámara será el doble de ancha que alta (y en consonancia lo grabado será el doble de estrecho de lo normal). Para ver la diferencia, intercalamos una instrucción yield que -como ya sabemos- suspende la ejecución de cualquier otra instrucción (en este caso durante cinco segundos) y acto seguido reseteamos el aspect de la cámara para devolverlo a sus parámetros normales.
WorldToScreenPoint: function WorldToScreenPoint (position : Vector3) : Vector3
Transforma la posición de un transform desde el espacio global al espacio de la pantalla. Recordemos que el espacio de la pantalla está definido en píxeles (izquierda-abajo es (0,0) y derechaarriba es (pixelWidth,pixelHeight). La posición z se calcula en unidades globales desde la cámara. Un ejemplo. Editamos MiSegundoScript tecleando esto:
var eseQueTuVesAhi : Transform; var posicionGlobal : String; var posicionEnPixeles : String; posicionGlobal = eseQueTuVesAhi.position.ToString(); posicionEnPixeles = camera.WorldToScreenPoint(eseQueTuVesAhi.position).ToString(); Debug.Log("La posicion global es " + posicionGlobal + " y la posicion en pixeles " +posicionEnPixeles);
Guardamos y arrastramos desde la Jerarquía nuestro Cubo hasta la variable expuesta eseQueTuVesAhi. El script devuelve primero la posición global del cubo en la escena, en un string generado por la función ToString. Acto seguido convertimos ese vector3 que contiene las coordenadas globales en otro Vector3 que contiene la ubicación en píxeles del transform del cubo respecto de la pantalla. Al devolver un Vector3, podemos también aprovechar la función ToString para convertir dichas coordenadas en un String e imprimirlas. Recordemos que el tercer parámetro del Vector3 con la ubicación en píxeles viene referido (eje Z) a la distancia en unidades globales (metros) entre la cámara y el transform.
WorldToViewportPoint: function WorldToViewportPoint (position : Vector3) : Vector3
Convierte la posición de un transform desde las coordenadas globales al espacio de punto de vista (viewport space en inglés). El viewport space es el relativo a la cámara, donde izquierda-abajo es (0,0) y derecha-arriba (1,1). La posición z se mediría en unidades globales desde la cámara.
Lo vemos con más claridad modificando ligeramente el script anterior:
var eseQueTuVesAhi : Transform; var posicionGlobal : String; var posicionEnEspacioCamara : String; posicionGlobal = eseQueTuVesAhi.position.ToString(); posicionEnEspacioCamara = camera.WorldToViewportPoint(eseQueTuVesAhi.position).ToString(); Debug.Log("La posicion global es " + posicionGlobal + " y la posicion en espacio normalizado de camara " +posicionEnEspacioCamara);
Como vemos al darle al play, ahora se nos muestra en coordenadas relativas a la cámara la ubicación del transform del cubo. Continúa inalterada, eso sí, la distancia respecto del eje Z (profundidad), que ya dijimos que va medido en unidades globales respecto de la posición de la cámara.
54. CLASE CAMERA (V)
ViewportToWorldPoint: function ViewportToWorldPoint (position : Vector3) : Vector3
Es la función inversa a WorldToViewportPoint, que estudiamos en la lección anterior. Convierte por tanto la posición de un transform medida en el viewport space relativo de la cámara (0,0 a 1,1) a coordenadas globales. Se suministra a la función un vector donde los componentes X e Y son las coordenadas de pantalla, y el componente Z es la distancia del plano resultante desde la cámara en unidades globales, y la función los transforma en coordenadas globales. Así, podemos adaptar el ejemplo que nos propone el manual de referencia y dibujar una esfera amarilla en la esquina superior derecha de la pantalla, y traducir dichas coordenadas de ubicación a las generales de la escena.
function OnDrawGizmos () { var deLaCamaraAlMundo : Vector3 = camera.ViewportToWorldPoint (Vector3 (1,1,0.5)); Gizmos.color = Color.yellow; Gizmos.DrawSphere (deLaCamaraAlMundo, 0.1); }
Le hemos dado una profundidad a la ubicación de la esfera de 0.5. Hemos de tener presente que esta distancia no debe ser inferior a la que esté establecida en nearClipPlane, o no se verá.
ScreenToWorldPoint: function ScreenToWorldPoint (position : Vector3) : Vector3
Convierte la posición de un transform desde el espacio de pantalla en píxeles (0,0 a pixelWidth,pixelHeight) a coordenadas globales, con la posición z (como en el resto de casos) medida en unidades globales desde la cámara.
ScreenToViewportPoint: function ScreenToViewportPoint (position : Vector3) : Vector3
Convierte la posición de un transform de espacio de pantalla en píxeles (0,0 a pixelWidth,pixelHeight) a viewport space relativo al espacio de cámara (0,0 a 1,1).
ViewportToScreenPoint: function ViewportToScreenPoint (position : Vector3) : Vector3
Convierte la posición del transform de viewport space relativo al espacio de cámara (0,0 a 1,1) en espacio de pantalla en píxeles (0,0 a pixelWidth,pixelHeight). La posición z en ambos tipo de medida es la misma, medida en unidades globales desde la cámara.
ViewportPointToRay: function ViewportPointToRay (position : Vector3) : Ray
Devuelve un rayo que sale de la cámara en coordenadas relativas a ésta (0,0 a 1,1). El rayo comienza en el plano más cercano a la cámara, razón por la que la posición Z es ignorada.
ScreenPointToRay: function ScreenPointToRay (position : Vector3) : Ray
Devuelve un rayo que va de la cámara a través de un punto de la pantalla. Estando el rayo en las coordenadas globales, empieza en el plano cercano a la cámara y va a través de la posición x e y en las coordenadas de píxeles (0,0 a pixelWidth,pixelHeight) en la pantalla (la posición z es ignorada.)
55. CLASE CAMERA (VI)
Render: function Render () : void
Renderiza la cámara manualmente, usando el clear flags de la cámara, target texture y otras propiedades. La cámara puede enviar mensajes como OnPreCull, OnPreRender o OnPostRender a cualquier script que esté vinculado, y renderizar algunos filtros de imagen. Esto es usado para tener control preciso sobre el orden de renderizado. Para hacer uso de esta característica, crea una cámara y deshabilitala, y entonces llama a la función Render para ella.
RenderWithShader: function RenderWithShader (shader : Shader, replacementTag : String) : void
Hace que la cámara renderice con reemplazo de sombreado (shader) . Esto renderizará la cámara, usando los clear flags de la cámara, target texture y otras propiedades. A direrencia de la función anterior, la cámara no enviará OnPreCull, OnPreRender or OnPostRender a scripts vinculados. Los filtros de la imagen tampoco serán renderizados. Esto es usado para efectos especiales, como por ejemplo renderizar una visión de calor y cosas así. Para usar estas características, habitualmente crearás una cámara y la deshabilitarás y luego llamarás RenderWithShader en ella.
SetReplacementShader: function SetReplacementShader (shader : Shader, replacementTag : String) : void
Hace que la cámara renderice con shader replacement. Después de llamar a esta función, la cámara renderizará su vista con shader replacement. Para volver al renderizado normal hay que llamar a ResetReplacementShader.
ResetReplacementShader: function ResetReplacementShader () : void
Borra el shader replacement de la cámara provocado por la función anterior.
RenderToCubemap: function RenderToCubemap (cubemap : Cubemap, faceMask : int = 63) : boolean
Renderiza un cubemap desde la cámara que llama a esta función. Esto es muy útil en el editor para bakear cubemaps estáticos de tu escena. La posición de la cámara, clear flags y clipping plane distances son usados para renderizar dentro de las caras de un cubemap. faceMask es un mapa de bits que indica qué cara del cubemap debe ser renderizada. Cada bit se corresponde a una cara y está representado por un int dentro de una enum de tipo cubemapface. Por defecto las seis caras del cube map se renderizarán, que es lo que viene representado en la firma por el 63, que corresponde a lo que ocupan los bits de las seis caras. La función devuelve false si el renderizado del cubemap falla. Esta función tiene un segundo prototipo: function RenderToCubemap (cubemap : RenderTexture, faceMask : int = 63) : boolean
Esta segunda modalidad es usada para reflexiones en tiempo real dentro de render textures de cubemap. Puede ser bastante caro en términos de rendimiento, eso sí, especialmente si las seiz caras del cubemap son renderizadas cada frame.
CopyFrom: function CopyFrom (other : Camera) : void
Permite copiar para una cámara todas las variables de otra cámara (campo de vision, clear flags, culling mask…) Puede ser útil si queremos que una cámara coincida con la configuración de otra, para conseguir
efectos personalizados de rendering, como por ejemplo los objetidos usando RenderWithShader.
VARIABLES DE CLASE: main: static var main : Camera
Se refiere a la cámara que esté habilitada y con el tag “Main Camera”(Read Only). Devuelve nulo si no hay una cámara con esas características en la escena.
allCameras: static var allCameras : Camera[]
Devuelve todas las cámaras habilitadas en la escena.
56. CLASE LIGHT (I)
Esta clase se usa para controlar todos los aspectos de las luces en Unity. Las propiedades que aquí veremos son exactamente las mismas que los valores que podemos encontrarnos en el inspector. Normalmente las luces son creadas en el editor, pero a veces puede ser que queramos crear o manipular una luz desde un script. Podemos, para tener una primera idea de lo que podemos hacer con esta clase, aplicar el ejemplo que nos viene en el manual de referencia. Antes de nada, modifiquemos el escenario para los ejemplos que
vendrán: 1.- Eliminamos en Jerarquía Camara 2. 2.- Eliminamos el script vinculado a PortaScripts. Doble click en MiPrimerScript. Tecleamos:
function Start () { var unaNuevaLuz : GameObject = new GameObject("La luz"); unaNuevaLuz.AddComponent(Light); unaNuevaLuz.light.color = Color.blue; unaNuevaLuz.transform.position = Vector3(0, 5, 0); }
Salvamos y arrastramos el script a PortaScripts. Pulsamos play. Debería aparecer una nueva luz en la escena, tal que así:
La dinámica del script es sencilla: Creamos primero un gameobject (que sería el equivalente a crear en el menú de la interface de Unity un gameobject vacío. A dicho gameobject vacío (que damos en llamar "la luz")le añadimos un componente de tipo luz (por defecto se crea una luz de tipo Point). Le damos por último a esa luz recién creada un color y una ubicación en la escena.
VARIABLES: type: var type : LightType
El tipo de luz. Puede ser LightType.Spot: Consiste en una luz tipo tipo foco. LightType.Directional: Una luz direccional, parecida a la del sol. LightType.Point: Un punto de luz. Para mostrar la diferencia entre las tres, he apagado la luz principal de la escena deshabilitando su checkbox, y he rediseñado el script anterior, y a su vez he hecho las pertinentes probaturas con los tres tipos de luz.
function Start () { var unaNuevaLuz : GameObject = new GameObject("La luz"); unaNuevaLuz.AddComponent(Light); unaNuevaLuz.transform.position = Vector3(0, 3, 0); unaNuevaLuz.light.type = LightType.Spot; }
AquĂ os dejo las capturas de pantalla de este script con los tres tipos de luz. Primero con luz tipo Spot:
Luz directional:
Y luz tipo point:
57. CLASE LIGHT (II)
color: Var color : Color
El color de la luz. Para modificar la intensidad de la luz podemos cambiar el color de la luz. Por ejemplo, una luz de color negro es lo mismo que no tener ninguna luz.
intensity: var intensity : float
La intensidad de la luz es multiplicada con el color de la luz. El valor puede estar entre 0 y 8. Esto nos permite crear luces muy brillantes. Realicemos un pequeño ejemplo. Previamente eliminamos el script vinculado a PortaScripts y volvemos a habilitar (si lo habíamos desmarcado) el checkbox de nuestra luz principal. Doble click en MiPrimer Scipt. Tecleamos:
function Update(){ var opaco : float = 0.0; var brillante : float = 8.0; light.color = Color.green; light.intensity = Random.Range(opaco, brillante); }
Guardamos y vinculamos el script a nuestra Luz. Al darle al play deberíamos asistir a una escena iluminada por una parpadeante luz de neón. Meramente lo que hemos hecho es utilizar una función de la clase Random (de próximo estudio)de nombre Range, que genera un número aleatorio entre un máximo y un mínimo. Dado que la intensidad de la luz va de 0 a 8, le damos ese amplio margen de actuación a Random.Range para que cada frame varíe la intensidad de la luz de manera aleatoria entre 0 y 8. Ello, unido al color verde típicamente apocalíptico, nos da este bonito efecto.
shadows: var shadows : LightShadows
Indica cómo proyecta sombras la luz. Esta variable es de tipo LightShadows, que es una enumeración que permite los siguientes valores: None: No proyecta sombras (por defecto) Hard: Proyecta sombras duras (sin filtro de sombras) Soft: Proyecta sombras suaves (con filtro) Pongamos un ejemplo. Doble click sobre MiPrimerScript. Tecleamos:
function Start(){ light.color = Color.yellow; light.intensity = 3.0; light.shadows = LightShadows.Hard; }
Observemos las sombras, y comprenderemos por qué se les denomina como duras:
Y si usamos el valor LightShadows.Soft:
shadowStrenght: var shadowStrength : float
Establece/indica la fuerza de las sombras.
shadowBias: var shadowBias : float VendrĂa a ser la perpendicular de la sombra respecto del objeto que la emite.
shadowSoftness:
var shadowSoftness : float
Suavidad de las sombras de las luces direccionales.
shadowSoftnessFade: var shadowSoftnessFade : float
Velocidad de fadeout de las sombras de las luces direccionales.
58. CLASE LIGHT (y III)
range: var range : float
El diámetro del rango o haz de luz, en luces de tipo spot y point.
spotAngle: var spotAngle : float
En ángulo en grados de las luces de tipo spotlight. Usado originalmente en luces de tipo spot, altera el tamaño del foco de la galleta de luz en luces de tipo direccional. No tiene efectos para luces de tipo point.
cookie: var cookie : Texture
Variable de tipo textura que establece la textura de la galleta proyectada por la luz.
flare: var flare : Flare
Selecciona un halo para la luz. Este ha de ser previamente asignado en el inspector.
renderMode: var renderMode : LightRenderMode
Establece cómo renderizar la luz, de entre las siguientes opciones que la enumeración LightRenderMode permite: Auto: Elige el modo de renderizado automáticamente. ForcePixel: Fuerza a la luz a ser una luz de píxeles. ForceVertex: Fuerza a la luz a ser una luz de vértices.
cullingMask: var cullingMask : int
Es usado para separar luces de la escena de manera selectiva. Si el layerMask del gameobject y el cullingMask de la luz son cero, entonces el objeto no será iluminado con esta luz.
59. CLASE MATERIAL (I)
Como podéis comprobar en el esquema superior, hemos subido dos peldaños en la jerarquía respecto a
las últimas clases que estábamos estudiando, y vamos ahora a aproximarnos a una serie de clases que derivan directamente de la clase base Object. La clase material, tal como es de suponer, accede a todas las propiedades de un material, permitiéndonos alterarlas/animarlas. Si pretendemos referirnos al material usado por un objeto, es preferible usar la propiedad Renderer.material, tal que así:
renderer.material.color = Color.red;
VARIABLES: shader: var shader : Shader Es el shader del material. En modelado 3d, un shader vendría a ser el algoritmo que indica cómo una superficie ha de responder ante la luz. Es una variable de la clase Shader (que estudiaremos en su momento). Nos permite utilizar/crear/importar diferentes reacciones que tendrá nuestro objeto al darle la luz (diffuse, transparent, ect) Por ejemplo, podemos comprobar qué tipo de shader tiene nuestra esfera. Para ello, antes que nada, eliminamos el script que tenemos vinculado a la luz (si es que estamos siguiendo las lecciones por orden), y editamos MiPrimerScript tal como sigue:
renderer.material.shader = Shader.Find( "Transparent/Diffuse" ); var miShader : String; miShader = renderer.material.shader.ToString(); Debug.Log(miShader);
Como vemos, para obtener el tipo de material referido a un objeto recurrimos a la clase Renderer. Observamos que nuestra esfera tiene el shader por defecto, que es de tipo Difusse. Y si queremos cambiar el tipo de shader y convertirlo en especular, usamos la función Find (que estudiaremos en su momento cuando demos la clase Shader) y se la asignamos a nuestra esfera.
var miShader : String; renderer.material.shader = Shader.Find( "Specular" ); miShader = renderer.material.shader.ToString(); Debug.Log(miShader);
color: var color : Color
El color principal del material. Es lo mismo que -como veremos en breve- usar GetColor o SetColor con el nombre “_Color”. Añadimos una declaración a nuestro script para cambiarle el color a la esfera:
var miShader : String;
renderer.material.shader = Shader.Find( "Specular" ); renderer.material.color = Color.cyan; miShader = renderer.material.shader.ToString(); Debug.Log(miShader);
mainTexture: var mainTexture : Texture La textura principal del material. Es lo mismo que usar GetTexture o SetTexture con el nombre “_MainTex”. Si por ejemplo escribiéramos un script así:
var texture : Texture; renderer.material.mainTexture = texture;
tendríamos una variable "expuesta" donde podríamos arrastrar la textura principal que quisiéramos que tuviera nuestro material.
mainTextureOffset: var mainTextureOffset : Vector2 Nos permite desplazar la textura principal. Hay un ejemplo muy indicativo en el manual de referencia de Unity, que he modificado ligeramente. Con carácter previo necesitaría que os descargárais una textura, a poder ser alguna cuyo movimiento fuera perceptible. Yo por ejemplo voy a utilizar esta: http://www.blogger.com/img/blank.gif Una vez descargada, la guardamos dentro de la carpeta "assets" de nuestro proyecto en Unity, de tal manera que en Proyecto nos salga. Acto seguido escribimos este script:
var miTextura : Texture; renderer.material.mainTexture = miTextura; var scrollSpeed : float = 0.5; function Update() { var offset : float = Time.time * scrollSpeed; renderer.material.mainTextureOffset = Vector2 (offset, 0); }
Salvamos y arrastramos la textura hasta la variable expuesta del script miTextura. Le damos al play y observaremos que la textura de nuestra esfera comienza a girar (ojo, si vemos las variables rotate de la esfera observaremos que no es el objeto el que gira, sino la textura la que se desplaza sobre el objeto). Meramente estamos cambiando de sitio a nuestra textura con respecto a nuestro objeto (pensemos en aplicaciones como anuncios de neón en una ciudad futurista).
60. CLASE MATERIAL (II)
mainTextureScale: var mainTextureScale : Vector2
La escala de la textura principal.
FUNCIONES: Constructor: static function Material (contents : String) : Material
Crea un material temporal a partir de un shader descrito por un string. static function Material (shader : Shader) : Material O bien crea un material temporal directamente desde un shader que le proporcionemos de entre los que tengamos. A grandes rasgos la creación de un nuevo material sería así:
var shader : Shader; var texture : Texture; var color : Color; function Start () { renderer.material = new Material (shader); renderer.material.mainTexture = texture; renderer.material.color = color; }
Arrastrándole el shader, material y color, o cuanto menos el primero, creamos un material totalmente nuevo. static function Material (source : Material) : Material
Crea un material temporal copiando el shader y todas las propiedades del material que le pasamos como parámetro.
SetColor: function SetColor (propertyName : String, color : Color) : void
Nos permite indicar el nombre para un determinado color. Muchos shaders usan más de un color, por ejemplo:
"_Color" es el color principal del material, que es el que puede ser también accedido desde la propiedad "color" de la clase. "_SpecColor" es el color especular de un material (usado en shaders specular/glossy/vertexlit). "_Emission" es el color que emite un material (usado en vertexlit shaders). "_ReflectColor" es el color de reflexión del material (usado en reflective shaders).
En el ejemplo siguiente, sacado del manual de referencia, asignamos el shader glossy (brillante) a nuestra esfera, y establecemos su color especular en rojo:
function Start () { renderer.material.shader = Shader.Find ("Glossy"); renderer.material.SetColor ("_SpecColor", Color.red); }
GetColor: function GetColor (propertyName : String) : Color
Obtiene el valor de un color con nombre. Por ejemplo:
print (renderer.material.GetColor("_SpecColor"));
SetTexture: function SetTexture (propertyName : String, texture : Texture) : void
Establece el nombre de una textura. Permite cambiar el propertyName de la textura. Los nombres comunes de las texturas que encontramos ya creadas en Unity son: "_MainTex" es la textura difusa principal. Puede ser accedida también a través de la propiedad mainTexture. "_BumpMap" es el mapa de normales. "_Cube" se refiere al cubemap.
GetTexture: function GetTexture (propertyName : String) : Texture Obtiene el nombre de una textura.
61. CLASE MATERIAL (y III)
SetTextureOffset: function SetTextureOffset (propertyName : String, offset : Vector2) : void
Establece el lugar de desplazamiento de la textura pasada como primer parámetro.
GetTextureOffset: function GetTextureOffset (propertyName : String) : Vector2
Obtiene la ubicación del desplazamiento de la textura pasada como parámetro.
SetTextureScale: function SetTextureScale (propertyName : String, scale : Vector2) : void
Establece la escala de la textura pasada como primer parámetro.
GetTextureScale: function GetTextureScale (propertyName : String) : Vector2
Obtiene la escala de la textura pasada como parámetro.
SetFloat: function SetFloat (propertyName : String, value : float) : void
Establece un valor tipo float con nombre.
GetFloat: function GetFloat (propertyName : String) : float
Obtiene un valor de tipo float con nombre.
HasProperty: function HasProperty (propertyName : String) : boolean
Comprueba si el shader del material tiene una propiedad con un nombre determinado. Por ejemplo:
if(renderer.material.HasProperty("_Color")) renderer.material.SetColor("_Color",Color.red);
GetTag: function GetTag (tag : String, searchFallbacks : boolean, defaultValue : String = "") : String
Obtiene el valor del tag del shader del material. Si el shader del material no tiene definido el tag, devuelve defaultValue. Si el parámetro searchFallbacks es true, entonces esta función buscará el tag en todos los
subshaders. Si searchFallbacks es falso entonces sólo se hará la consulta para el actual subshader.
Lerp: function Lerp (start : Material, end : Material, t : float) : void
Interpola propiedades entre dos materiales. Hace que todos los colores y valores del primer material sean convertidos en los valores del segundo material en el tiempo t. Cuanto el tercer parámetro (t) es cero, se toman los valores de start; cuando es 1, los valores se toman de end.
CopyPropertiesFromMaterial: function CopyPropertiesFromMaterial (mat : Material) : void
Copia propiedades de otro material en este material.
62. CLASE CUBEMAP
Como vemos, esta clase deriva directamente de Material, que vimos en la lección pasada. No es quizás la clase que más vayamos a utilizar, ya que por norma general utilizaremos tal cual los cubemaps con los que contemos, pero tampoco está de más tener un conocimiento mínimo de la clase. VARIABLES: format: var format : TextureFormat
El formato de los datos de los píxeles en la textura. Sólo lectura.
FUNCIONES: Cubemap: static function Cubemap (size : int, format : TextureFormat, mipmap : boolean) : Cubemap
Crea una nueva textura de cubemap. La textura puede tener tamaño en cada lado y con o sin mipmaps.
SetPixel: function SetPixel (face : CubemapFace, x : int, y : int, color : Color) : void
Coloca el color del pixel en las coordenadas (face, x, y)
GetPixel: function GetPixel (face : CubemapFace, x : int, y : int) : Color
devuelve el color del pixel en las coordenadas (face, x, y)
GetPixels: function GetPixels (face : CubemapFace, miplevel : int = 0) : Color[]
Devuelve el color del pixel de una cara del cubemap. Devuelve un array de colores de píxeles de una cara del cubemap.
SetPixels: function SetPixels (colors : Color[], face : CubemapFace, miplevel : int = 0) : void
Establece colores de pixeles de una cara del cubemap. Esta función toma un array de color y cambia los colores de los píxeles de la cara completa del cubemap.
Apply: function Apply (updateMipmaps : boolean = true) : void
Aplica en realidad todos los previos cambios de SetPixel y Setpixels.
63. CLASE RENDERTEXTURE (I)
Al igual que Cubemap, la clase RenderTexture hereda directamente de Material. Hemos de advertir antes que nada que esta clase sólo está disponible para Unity Pro, así que quien no tenga o confíe en tener la versión de pago de Unity puede saltarse tranquilamente estas lecciones. Render textures son texturas que pueden ser renderizadas. Pueden ser usadas para implementar imágenes basadas en efectos de renderizado, sombras dinámicas, proyectores, reflexiones o cámaras de vigilancia. Como es posible que -como a mí en su momento- esa definición no nos diga nada, vamos a acercarnos por la vía de los hechos a las rendertextures. Hemos de seguir los siguientes pasos: 1.- Eliminamos el script vinculado a la esfera.
2.- Al cubo, en scale, le damos los siguientes valores: 5,5,0.1, y en position: -2,2,0. 3.- En el menú, nos vamos a Assets=>Create=>Render Texture. 4.- Asimismo en el menú, le damos a Gameobject=>Create other=>camera. 5.- A esta nueva cámara le quitamos el componente Audio Listener, para que no nos dé problemas de duplicidad. 6.- Con camera seleccionada, en el inspector veremos una variable denominada Target Texture. Arrastramos hasta ahí la renderTexture creada antes y que está en Proyecto. 7.- Establecemos las siguientes coordenadas para camera: Position, 2,1,0, Rotation 90,0,0. (nos aseguramos de que la esfera esté en 2,0,0.). Deberíamos estar viendo en la vista previa de la nueva cámara la esfera muy de cerca. 8.-Arrastramos la Render Texture de Proyecto al cubo. 9.- Pulsamos play. Deberías estar viendo algo como esto:
Por lo tanto, observamos que render textures es un tipo especial de textura que se crea y actualiza en tiempo de ejecución, y que convierte en textura el fruto del renderizado de una cámara. Para ello observamos que necesitamos crear (en la interfaz o a través de un script) una nueva render textura y asignarla a una cámara para que la renderice. Acto seguido creamos o designamos una superficie que reciba esa textura así creada. Decimos que se crea y actualiza en tiempo de ejecución. Para demostrar tal aseveración, vamos a crear un pequeño script. Doble click en MiPrimerScript:
var miTextura : Texture; function Update() { renderer.material.mainTexture = miTextura; transform.Rotate(0,20,0); }
Si todavía tenemos en Proyecto la textura que nos descargamos hace un par de lecciones, la arrastramos a miTextura. Si no, hacemos lo propio con cualquier textura no uniforme (para que se note el efecto de la rotación de la esfera) que tengamos a mano. Le damos al play. Pensemos en las enormes posibilidades que nos abre esta clase, que nos permite convertir en textura o plasmar sobre cualquier superficie algo que está siendo grabado en ese momento en otro lugar (esto nos sirve tanto para implementar el contenido de una cámara de seguridad como para elaborar todo tipo de espejos o superficies reflectantes.) VARIABLES:
width: var width : int
La anchura de la textura renderizada en píxeles. A diferencia de Texture.width, esta variable no es de sólo lectura, y permite establecer un valor para cambiar la anchura.
height: var height : int
La altura de la textura renderizada en píxeles. Le es de aplicación lo dicho para width.
64. CLASE RENDERTEXTURE (II)
depth: var depth : int
La precisión en bits de la profundidad del búfer de la render texture (son soportados los valores 0, 16 y 24)
format: var format : RenderTextureFormat El formato de la render texture. RenderTextureFormat es una enumeración que permite estos valores: RenderTextureFormat es una enum con estos valores:
ARGB32: Depth: ARGBHalf: por RGB565:
Formato de color de la render texture, 8 bits por canal. Un formato de profundidad de la render texture. Formato de color de la render texture, 16 bit en punto flotante canal. Formato de color de la render texture.
ARGB4444: ARGB1555: Alpha, 5 Default: del
Formato de color de la render textura, 4 bit por canal. Formato de color de la render texture, 1 bit para el canal bits para los canales del rojo, verde y azul. Formato de color por defecto de la render texture, dependiendo formato de bufer por frame y la plataforma.
useMipMap: var useMipMap : boolean
Por defecto, las render textures no tienen mipmaps. Si establecemos esta variable en true, se generarán niveles de mipmap asociados. Este flag puede ser usado sólo en render textures que sean potencias de dos.
var isCubemap : boolean var isCubemap : boolean Si está habilitada, esta render texture será usada como Cubemap.
FUNCIONES: RenderTextures: static function RenderTexture (width : int, height : int, depth : int, format : RenderTextureFormat) : RenderTexture
Crea un nuevo objeto RenderTexture, que es creado con anchura y altura, con un buffer de profundidad y en un determinado formato. Sería lo mismo que hicimos en el ejemplo de la lección anterior, pero a través de un script. Cuando invocamos a través del constructor un nuevo objeto RenderTexture no estamos todavía creándolo en realidad . El RenderTexture será creado o bien la primera vez que se usa o bien llamando de manera expresa a la función Create. Así que después de construir la render texture, dado que aún no ha tomado forma la representación final de la misma, es posible establecer variables adicionales, como format, isCubemap y similares. static function RenderTexture (width : int, height : int, depth : int) : RenderTexture Este segundo prototipo del constructor es idéntico al anterior, salvo que no se establece de manera expresa el formato de la rendertexture. La render texture es colocada para estar en color format por defecto.
Create: function Create () : boolean
Tal como acabamos de indicar, es esta función la que crea en realidad la RenderTexture.
Release: function Release () : void
Esta función libera los recursos de hardware usados por la render texture. La texture en sí no es
destruida, y será automáticamente creada otra vez cuando se use.
IsCreated: function IsCreated () : boolean
Indica si la render texture se ha creado realmente o no.
65. CLASE RENDERTEXTURE (y III)
DiscardContents: function DiscardContents () : void
Descarta el contenido de la render texture.
SetGlobalShaderProperty: function SetGlobalShaderProperty (propertyName : String) : void
Asigna esta render texture como la propiedad shader global llamada en el parámetro propertyName.
VARIABLES DE CLASE: active: static var active : RenderTexture
Se refiere a la render texture activa. Todos los renderings van dentro de la rendertexture activa. Si esta es null todo se renderiza en la ventana principal. Cuando una RenderTexture se convierte en activa su contexto de hardware de renderizado es automáticamente creado si no se había creado ya.
FUNCIONES DE CLASE: GetTemporary: static function GetTemporary (width : int, height : int, depthBuffer : int = 0, format : RenderTextureFormat = RenderTextureFormat.Default) : RenderTexture
Asigna una render texture temporal. Esta función es optimizada para cuando necesitas una RenderTexture rápida para hacer algunos cálculos temporales. Libérala usando ReleaseTemporary tan pronto hayas hecho el trabajo, así podrás reusarla otra vez en una nueva llamada si la necesitas.
ReleaseTemporary: static function ReleaseTemporary (temp : RenderTexture) : void
Libera una textura temporal asignada con GetTemporary.
66. CLASE PARTICLEEMITTER (I)
Antes que nada, pediros disculpas porque esa clase se me había pasado. Como podéis ver en el gráfico, ParticleEmitter deriva de Component (como Collider, Rigidbody, Transform...) y por lo tanto la debería haber explicado antes, pero bueno, subsanamos el error ahora. Vamos a preparar antes que nada nuestra escena para acoger los nuevos ejemplos de esta clase: 1.- Borramos la cámara que creamos en la clase RenderTexture 2.- Devolvemos el cubo a sus valores previos: position (-2,0,0) scale(1,1,1) 3.- Eliminamos el script vinculado a la esfera. 4.- Eliminamos la render texture en la carpeta Proyecto. 5.- Nos vamos al menú Gameobject=>Create other=>Particle System.
6.- Ubicamos nuestro nuevo gameobject en 0,0,0 y lo renombramos como "Particulas". 7.- Salvamos la escena. Deberíamos tener ahora una escena parecida a esta:
Vale, ya estamos listos. VARIABLES: emit: var emit : boolean
Booleano que indica si deben las partículas ser automáticamente emitidas cada frame o no. Podemos usar esta variable para "encender o apagar" la emisión de partículas. Editemos nuestro script de la siguiente manera:
yield WaitForSeconds(5); particleEmitter.emit = false; yield WaitForSeconds(5); particleEmitter.emit = true;
Observaremos que tras los cinco segundos iniciales, nuestro sistema de partículas comienza a extinguirse hasta desaparecer, y pasados otros cinco, vuelve a encenderse.
minSize: var minSize : float
El tamaño mínimo que cada partícula puede tener cuando se genera.
maxSize: var maxSize : float
El tamaño máximo que cada particular puede tener al tiempo de ser generada. Si tenemos el gameobject Particulas seleccionado, podemos comprobar que por defecto el tamaño mínimo (y máximo) es 0.1. Veamos qué pasa si alteramos estos valores:
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5;
minEnergy: var minEnergy : float
El mínimo tiempo de vida de cada particular, medido en segundos.
maxEnergy: var maxEnergy : float
El máximo tiempo de vida de cada partícula, medido en segundos. En el inspector esta variable y la anterior está colocada en 3 por defecto. Añadamos al script anterior unos valores distintos.
particleEmitter.minSize = particleEmitter.maxSize = particleEmitter.minEnergy particleEmitter.maxEnergy
0.2; 0.5; = 1; = 5;
Vemos que conseguimos así una mayor variedad en la persistencia de cada partícula, dándole mayor dinamismo a nuestro sistema de partículas. Dejamos aquí esta lección. Si aún te queda algo de tiempo no cierres el editor de scripts, ya que seguiremos en la próxima lección añadiendo modificaciones a nuestro ejemplo.
67. CLASE PARTICLEEMITTER (II)
minEmission: var minEmission : float
El mínimo número de partículas que serán generadas cada segundo.
maxEmission: var maxEmission : float
El máximo número de partículas que serán generadas cada segundo. Los valores por defecto en el inspector son 50. Editemos nuestro script para alterarlo.
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110;
Tenemos ahora un sistema de partículas más denso y compacto, como vemos.
emitterVelocityScale: var emitterVelocityScale : float
La cantidad de la velocidad de emisión que las partículas heredan. Por defecto 0.05.
worldVelocity: var worldVelocity : Vector3
La velocidad inicial de las partículas en el mundo global, a lo largo de x,y,z.
Incorporemos estas dos últimas variables a nuestro ejemplo, dándole a nuestro sistema de partículas una mayor velocidad de emisión y haciendo que nuestras partículas se desplacen hacia arriba (en coordenadas globales):
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.worldVelocity = Vector3(0,3,0);
localVelocity: var localVelocity : Vector3 Es como la anterior, pero el desplazamiento se efectúa en las coordenadas locales del propio sistema de partículas.
rndVelocity: var rndVelocity : Vector3 Una velocidad aleatoria con relación a los ejes x,y,z que es añadida a la velocidad. En nuestro ejemplo:
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.worldVelocity = Vector3(0,3,0); particleEmitter.rndVelocity = Vector3(1,0.5,0.5);
useWorldSpace: var useWorldSpace : boolean
Si está habilitado, las partículas no se mueven cuando el emitter se mueve. Si está en false, cuando muevas el emitter, las partículas lo siguen.
rndRotation: var rndRotation : boolean
Si está habilitado, las partículas serán generadas con rotaciones aleatorias. Por defecto está en false, así que observemos qué ocurre si lo habilitamos:
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.worldVelocity = Vector3(0,3,0); particleEmitter.rndVelocity = Vector3(1,0.5,0.5); particleEmitter.rndRotation = true;
angularVelocity: var angularVelocity : float La velocidad angular de las nuevas partículas en grados por segundo.
rndAngularVelocity: var rndAngularVelocity : float
Un modificador aleatorio de velocidad angular para nuevas partículas. Un valor aleatorio en el rango de [rndAngularVelocity,rndAngularVelocity] será aplicado a todas las nuevas partículas, en adición a ParticleEmitter. angularVelocity.
particleEmitter.minSize = particleEmitter.maxSize = particleEmitter.minEnergy particleEmitter.maxEnergy
0.2; 0.5; = 1; = 5;
particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.worldVelocity = Vector3(0,3,0); particleEmitter.rndVelocity = Vector3(1,0.5,0.5); particleEmitter.rndRotation = true; particleEmitter.angularVelocity = 1; particleEmitter.rndAngularVelocity = 66;
68. CLASE PARTICLEEMITTER (y III)
particles: var particles : Particle[]
Devuelve una copia de todas las partículas y asigna un array de todas las partículas. Hemos de tener en cuenta que después de modificar el array de partículas debemos asignarlo de vuelta al particleEmitter para ver el cambio. Partículas con energía de cero o menos serán elmiminadas cuando se asignen las partículas. Así cuando se crea un completo nuevo array de partículas, necesitaremos colocar la energía de todas las partículas explicitamente.
particleCount: var particleCount : int
El corriente número de partículas. Variable de sólo lectura.
enabled: var enabled : boolean
Establece el ParticleEmitter en on o off. Un ParticleEmitter que no está habilitado no emitirá ninguna partícula, y las partículas emitidas no se animarán. Así, este valor nos permite pausar un sistema de partículas. Reutilicemos el ejemplo anterior para pausar nuestro sistema de partículas transcurridos cinco segundos.
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 70; particleEmitter.maxEmission = 110; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.worldVelocity = Vector3(0,3,0); particleEmitter.rndVelocity = Vector3(1,0.5,0.5); particleEmitter.rndRotation = true; particleEmitter.angularVelocity = 1; particleEmitter.rndAngularVelocity = 66;
yield WaitForSeconds(5); particleEmitter.enabled = false;
FUNCIONES: ClearParticles: function ClearParticles () : void
Borra todas las partículas del sistema de partículas.
Emit: function Emit () : void
Emite un número de partículas. Hace que el emitter escupa un número aleatorio de partículas que se establecen entre las propiedades minEmission y maxEmission. Probemos:
particleEmitter.minSize = 0.2; particleEmitter.maxSize = 0.5; particleEmitter.minEnergy = 1; particleEmitter.maxEnergy = 5; particleEmitter.minEmission = 20; particleEmitter.maxEmission = 150; particleEmitter.emitterVelocityScale = 0.4; particleEmitter.angularVelocity = 1; particleEmitter.rndAngularVelocity = 66; particleEmitter.Emit(); function Emit (count : int) : void Esta variante de la función emite el número count de partículas inmediatamente. Pasémosle el parámetro 300 a la función Emit del ejemplo anterior, por poner un caso muy exagerado. function Emit (pos : Vector3, velocity : Vector3, size : float, energy : float, color : Color) : void Este tercer prototipo de la función Emit emite una partícula individual con unos parámetros dados. Expliquemos brevemente qué representa cada parámetro: pos: La posición de la partícula. velocity: La velocidad de la partícula. size: El tamaño de la partícula. energy: El tiempo de vida restante de la partícula. color: El color de la partícula.
particleEmitter.Emit(Vector3.zero, Vector3.up, 0.4, 4, Color.yellow); Cuando le damos al play veremos una partícula en la posición central, con dirección hacia arriba, un tamaño de 0.4 unidades, que tardará 4 segundos en desvanecerse y tendrá color amarillo. function Emit (pos : Vector3, velocity : Vector3, size : float, energy : float, color : Color, rotation : float, angularVelocity : float) : void Y aún tenemos una tercera variante de la función, que incluye la rotación inicial de la partícula en grados y la velocidad angular por segundo.
particleEmitter.Emit(Vector3.zero, Vector3.up, 0.4, 4, Color.yellow, 75, 150); Simulate: function Simulate (deltaTime : float) : void Avanzado sistema de simulación de partículas por un tiempo dado. Es útil para precalentar un sistema de partículas, como si se estuviera gestando:
particleEmitter.Simulate(5);
69. CLASE MESH (I)
Esta clase permite crear o modificar mallas desde scripts. Las mallas contienen vértices y múltiples arrays de triángulos. Los arrays de triángulos son meramente índices dentro del array de vértices, tres índices para cada triángulo. Para cada vértice puede haber un normal, dos coordenadas de texturas, color y tangentes. Toda la información de los vértices es almacenada en arrays separados del mismo tamaño, así que si tu malla tiene 10 vértices, debe tener tambíen arrays de 10 de tamaño para normales y otros atributos.
VARIABLES: vertices: var vertices : Vector3[]
Devuelve una copia de la posición del vértice o asigna un nuevo array de posiciones de vértices. El número de vértices en la malla es cambiado asignando un array de vértices con un diferente número de vértices. Debemos tener en cuenta que si redimensionamos el array de vértices entonces todos los otros atributos (normales, colores, tangentes, Uvs) serán automáticamente redimensionados también. RecalculateBounds automáticamente será invocado si no se han asignado vértices a la malla cuando se establecieron los vértices.
normals: var normals : Vector3[]
Los normales de la malla. Si la malla no contiene normales un array vacío es retornado.
tangents: var tangents : Vector4[]
Las tangentes de la malla. Las tangentes son mayormente usadas en bump-mapped shaders. Una tangente es un vector de una unidad de longitud que sigue la malla a lo largo de la dirección de textura horizontal. Las tangentes en
Unity son representadas como Vector4 con componentes x,y,z definiendo el vector, y w usada para dar la vuelta a la binormal si es necesario. Unity calcula los otros vectores de supervicie (binormal) tomando un producto cruzado entre normal y tangente y multiplicando el resultado por la tangente w. Así w debe ser siempre 1 o -1. Debes calcular tangentes por ti mismo si planeas usar bump-mapped shaders en la malla. Asigna tangentes después de asignar normals o usando RecalculateNormals.
uv: var uv : Vector2[]
Las coordenadas de la textura base de la malla.
rv2: var uv2 : Vector2[]
El segundo conjunto de coordenadadas de textura de la malla, si lo hubiere.
bounds: var bounds : Bounds
El volumen de bordes de la malla. Esto es la caja de bordes alineadas en el eje de la malla en el espacio local (esto es, no afectada por el transform). Existe por otro lado la propiedad Renderer.bounds que retorna los bordes en espacio global.
colors: var colors : Color[]
Devuelve los colores del vértice de la malla. Si no hay colores devuelve un array vacío.
triangles: var triangles : int[]
Un array conteniendo todos los triángulos de la malla. El array es una lista de triángulos que contiene índices dentro del array de vértices. El tamaño del array de triángulos debe siempre ser múltiplo de 3. Los vértices pueden ser compartidos meramente indexando dentro del mismo vértice. Si la malla contiene múltiples sub mallas (materiales) la lista de triángulos contendrá todos los triángulos de todas las submallas. Cuando asignas un array de triángulos, subMeshCount es colocado a 1. Si quieres tener múltiples submallas, usa subMeshCount y SetTriangles. Es recomendable asignar el array de triángulos después de asignar el array de vértices para evitar errores de fuera de límite (out of bounds)
vertexCount: var vertexCount : int
Variable de sólo lectura que devuelve el número de vértices en la malla.
subMeshCount: var subMeshCount : int
El número de submallas. Cada material tiene una lista de triángulos separada.
boneWeights: var boneWeights : BoneWeight[]
La pintura de pesos de cada vértice. El tamaño del array es o el mismo que vertexCount o vacío. Cada vértice puede ser afectado por un máximo de 4 diferentes huesos. Las cuatro pinturas de peso deben sumar hasta 1.
70. CLASE MESH (y II)
FUNCIONES: Mesh: static function Mesh () : Mesh
Crea una malla vacía.
Clear: function Clear () : void
Limpia todos los datos de los vértices y todos los índices de los triángulos. Debes llamar esta función antes de reconstruir el array de triángulos.
RecalculateBounds: function RecalculateBounds () : void
Recalcula el volumen de bordes de la malla desde los vértices. Después de modificar los vértices debes llamar a esta función para asegurarte de que el volumen de bordes es correcto. Asignando triángulos automáticamente se recalcula el volumen de bordes.
RecalculateNormals: function RecalculateNormals () : void
Recalcula los normales de la malla desde los triángulos y vértices. Después de modificar los vértices es a menudo útil actualizar los normales para reflejar el cambio. Los normales son calculados desde todos los vértices compartidos. Las mallas importadas a veces no comparten todos los vértices. Por ejemplo un vértice en una costura UV se partirá en dos vértices. En consecuencia la función RecalculateNormals creará normales que no son lisos en las costuras Uv. Notemos también que RecalculateNormals no genera tangentes automáticamente, asi que los bumpmap shaders no trabajarán con la malla después de llamar a esta función. Nosotros podemos sin embargo proveer nuestras propias tangentes.
Optimize: function Optimize () : void
Optimiza la malla para mostrarla. Esta operación podría tomar un rato pero hace que la geometría se muestre más rápido. Debes usarla si generas una malla desde cero procedimentalmente y quieres un mayo rendimiento en tiempo de ejecución en lugar de un mayor tiempo de carga. Para modelos importados no debes nunca llamarla porque el import pipeline ya lo hace por ti.
GetTriangles: function GetTriangles (submesh : int) : int[]
Devuelve la lista de triángulos de la submalla. Una submalla es simplemente una lista de triángulos separada. Cuando el mesh renderer usa múltiples materiales, debes asegurarte de que hay tantas submallas como materiales.
SetTriangles: function SetTriangles (triangles : int[], submesh : int) : void
Establece la lista de triángulos para la submalla. Es recomentable asignar el array de triángulos después de asignar el array de vértices para evitar el error de fuera de bordes.
CombineMeshes: function CombineMeshes (combine : CombineInstance[], mergeSubMeshes : boolean = true, useMatrices : boolean = true) : void
Combina varias mallas dentro de la malla. Combinar mallas es útil para optimización de rendimiento. Si mergeSubMeshes es true, todas las mallas serán combinadas en una única submalla. En caso contrario cada malla irá dentro de una submalla diferente. Si todas las mallas comparten el mismo material, coloca esto a true. Si useMatrices es false, el transform matrices en la estructura CombineInstance será ignorado.
71. CLASE GAMEOBJECT (I)
Estamos en la clase central, en la clase con mayúsculas de Unity. De hecho, muchas de las clases que hasta la fecha hemos estudiado (y algunas que nos faltan) no son más que diferentes mimbres cuya función primordial era confluir en esta clase. No deberá sorprendernos, por tanto, que buena parte de las variables o propiedades de la clase GameObject no son sino instancias de las clases que ya hemos estudiado, de tal forma que cualquier gameobject en la escena (recordemos que todo lo que está en la escena son gameobjects) tenga la más amplia funcionalidad.
VARIABLES: isStatic: var isStatic : boolean Variable sólo utilizable vía interface que especifica si un objeto es estático. Esto es útil cuando estemos trabajando con occlusion culling, para determinar si un objeto puede ser considerado un oclusor estático. Para quien no sepa qué diantres es lo de occlusion culling, decir que es una característica que deshabilita el renderizado de objetos cuando no están siendo actualmente vistos por la cámara porque son oclusionados/tapados por otros objetos. El proceso de occlusion culling va a travé de la escena usando una cámara virtual construyendo una jerarquía de objetos potencialmente visibles. Estos datos serán usados en tiempo de ejecución por cada
cámara para identificar qué es visible y qué no. Necesitamos etiquetar todos los objetos de la escena que queramos que sean parte de la occlusion como Static en el inspector. La manera más rápida de hacerlo es convertir todos los objetos que queramos marcar como estáticos en hijos de un GameObject vacío y establecer éste como Static, eligiendo luego en la opción de diálogo que la condición de static afecte también a todos sus hijos. Después de eso, ya podemos tranquilamente desparentarlos del gameobject vacío, y seguirán teniendo la consideración de static.
transform: var transform : Transform
El transform del gameobject, si lo tiene. Null si no tiene ningún transform vinculado. Tal como explicaba antes, ésta va a ser la tónica de la mayoría de propiedades de la clase Gameobject: integrar objetos/instancias de diferentes clases para conformar la utilidad básica de nuestro juego: los gameobjects. Podemos demostrar que todo lo que está en la escena es un gameobject: Eliminamos antes que nada el gameobject particulas que creamos en la clase anterior. Si tenemos algún script vinculado al cubo o la esfera, los eliminamos. Vamos a editar ahora MiPrimerScript:
var unGameObject: GameObject; function Update() { unGameObject.transform.Rotate(0,5,0); }
El script debería resultarnos fácil a estas alturas. Declaramos una variable expuesta de tipo Gameobject, de tal manera que posteriormente podamos acceder al transform de la que arrastremos y rotar el gameobject sobre el eje Y. Salvamos. Arrastramos el script a PortaScripts en la jerarquía. Con PortaScripts seleccionado, arrastramos el cubo a la variable expuesta. Play. Tal como era de esperar, el cubo comienza a girar. Pero vamos a ver qué otros elementos en la escena son considerados por Unity Gameobjects, y por tanto susceptibles de tener un transform. Si con PortaScripts seleccionado nos vamos al inspector, observaremos que a la derecha de la variable expuesta que hemos inicializado con el cubo hay una pequeña flecha. Si hacemos click sobre ella, se nos abre un menú emergente con TODOS los gameobjects de la escena. De hecho hasta nuestro PortaScripts -a pesar de no ser ni visible- es considerado un gameobject. Así, si en ese mismo popup hacemos doble click en main camera y le damos al play, observaremos en la vista del juego que lo que empieza a girar es la cámara.
rigidbody: var rigidbody : Rigidbody
El rigidbody vinculado a nuestro gameobject, o null si éste no tiene rigidbody.
camera: var camera : Camera
La cámara vinculada a nuestro gameobject, o null si éste no tiene una cámara. Por ejemplo, nuestra main
camera es un gameobject que tiene vinculado una cámara (y un transform).
light: var light : Light
La luz vinculada al gameobject. Null si no tiene.
72. CLASE GAMEOBJECT (II)
animation: var animation : Animation
La animación vinculada al gameobject. Null si no tiene. No hemos tratado aún nada relacionado con animaciones, clips y sonidos, ya que he preferido reservarlo para más adelante y tratarlo todo junto.
constantForce: var constantForce : ConstantForce
la constantForce vinculada a este gameobject. Null si no tiene ninguna vinculada. No habíamos tratado todavía la miniclase ConstantForce, pero vamos a subsanar ese pequeño lapsus ahora mismo: La clase ConstantForce deriva de Behaviour (como camera, light, animation...) y cuenta sólo con cuatro variables/propiedades (amén de las heredadas), que son:
force: var force : Vector3 La fuerza aplicada al rigidbody cada frame. Esto último es lo que la diferencia de rigidbody.AddForce(). En AddForce se aplica la fuerza al rigidbody una vez por frame, lo que obliga a llamar a la función varias veces (por ejemplo dentro de una función fixedUpdate). constantForce.force aplicará la fuerza indicada cada frame hasta que cambiemos el contenido de la variable force a un nuevo valor. Esto es aplicable a las cuatro variables de esta clase. relativeForce: var relativeForce : Vector3 La fuerza -relativa al sistema de coordenadas local del rigidbody- aplicada cada frame. torque:
var torque : Vector3
La torsión aplicada al rigidbody cada frame. relativeTorque: var relativeTorque : Vector3 La torsión -relativa al sistema de coordenadas local del rigidbody- aplicada cada frame.
Pongamos un ejemplo global: Antes que nada seleccionamos el cubo, y nos vamos al menú=>Component=>Physics=>Constant Force. Editamos una vez más MiPrimerScript:
var unGameObject: GameObject; unGameObject.constantForce.force = Vector3(3,10,0); unGameObject.constantForce.torque = Vector3(0,8,0);
Salvamos. Recordemos que este script lo tenemos vinculado al gameobject PortaScripts, así que lo seleccionamos. Arrastramos el cubo a la variable expuesta. Observemos que ambos atributos están fuera de toda función o bucle, esto es, que tal como hemos dicho, tanto la fuerza (hacia la derecha y hacia arriba) como la torsión (sobre el eje Y) que le aplicamos tendrán carácter periódico debido única y exclusivamente a las propias características de la clase constantForce. Play.
renderer: var renderer : Renderer
El renderer vinculado al gameobject. Null si no tiene.
audio: var audio : AudioSource
el audiosource vinculado al gameobject. Null si no tiene.
73. CLASE GAMEOBJECT (III)
guiText: var guiText : GUIText El guiText (de próximo estudio) vinculado al gameobject. Nulo si no existe.
networkView: var networkView : NetworkView El networkView (a estudiar mucho más adelante) vinculado al gameobject. Nulo si no existe.
guiTexture: var guiTexture : GUITexture
El guiTEXture (en breve) vinculado al gameobject. Null si carece de él.
collider: var collider : Collider
El collider vinculado al gameobject, si lo tiene. Null en caso contrario.
hingeJoint: var hingeJoint : HingeJoint
El hingeJoint vinculado al gameobject, o null.
particleEmitter: var particleEmitter : ParticleEmitter
El particleEmitter vinculado al gameobject, null si no tiene.
layer: var layer : int
Es una variable de tipo int comprendida entre el rango 0 y 31 que indica/coloca la capa en la que el gameobject se encuentra. El layer sirve entre otras cosas para realizar un renderizado selectivo de lo que una cámara debe mostrar o para determinar si a un determinado gameobject les afectará o no un raycast.
active: var active : boolean
¿Está el gameobject activo?. Podemos habilitarlo/deshabilitarlo cambiando a true o false este booleano.
tag: var tag : String
El tag (etiqueta) de este game object. Puede ser usado para identificar a un gameobject. Hemos de recordar que antes de poder usar esta variable debemos haber declarado el tag en el inspector
74. CLASE GAMEOBJECT (IV)
FUNCIONES: GameObject: static function GameObject (name : String) : GameObject
La función constructora de gameobject tiene tres prototipos diferentes. El primero, como vemos, nos permite crear un nuevo gameobject y pasarle un parámetro que constituirá el nombre de dicho gameobject. Veamos un ejemplo sencillo. Editamos MiPrimerScript:
var nuevoGameObject: GameObject; nuevoGameObject = new GameObject("miNuevoObjeto");
Lo salvamos. Si no lo estaba, lo arrastramos a PortaScripts. Le damos al play. Aparentemente no ocurre nada. No aparece ningún objeto en la escena ni en la ventana del juego. Esto es porque nuestro nuevo gameobject está vacío, como podremos comprobar si -con el juego reproduciéndose- seleccionamos el nuevo gameobject de nombre miNuevoObjeto que aparece en la jerarquía. Observamos que tan sólo tiene un elemento transform (que se crea por defecto). Pero, ya teniendo un gameobject creado en tiempo de ejecución (y volveremos sobre este concepto pasado no demasiado tiempo), podemos de la misma forma añadirle componentes:
var nuevoGameObject: GameObject; nuevoGameObject = new GameObject("miNuevoObjeto"); nuevoGameObject.AddComponent ("BoxCollider"); nuevoGameObject.AddComponent ("MeshRenderer");
En este caso le añadimos un collider de cubo y una meshrenderer. Seguirá sin verse nuestro nuevo gameobject -salvo que lo seleccionemos en la jerarquía, en cuyo caso veremos la malla) porque para ello le deberíamos haber creado una malla desde cero, pero a los efectos de este ejemplo con esto debería bastar. static function GameObject () : GameObject
Crea un gameobject, pero sin nombre nombre, lo cual no obsta para que luego se le pueda asignar uno.
static function GameObject (name : String, params components : Type[]) : GameObject Esta variante del constructor crea un gameobject con nombre y ya vinculado a una serie de componentes prefijados.
GetComponent: function GetComponent (type : Type) : Component
Esta función devuelve el componente de tipo Type que tenga el gameobject. En caso de que el gameobject que lanza el mensaje no tenga ningún componente de ese tipo, devuelve null. Además de acceder a componentes Standard de unity, puedes acceder a scripts por esta vía. En este caso, el nombre del script equivaldrá al Type del parámetro de búsqueda. Ojo, porque aunque pongamos el nombre del script, como en realidad sustituye y equivale a un tipo de componente, lo escribiremos sin comillas (no es un string) Es decir, si por ejemplo quisiéramos -no hace falta que realicéis este ejemplo, es sólo para aclarar conceptos- hacer algo en caso de que un determinado gameobject tenga un determinado script, haríamos algo como esto:
var unObjetoCualquiera : GameObject; if(unObjetoCualquiera.GetComponent(miPrimerScript) { HazAlgunaCosa(); }
Arrastraríamos luego el gameobject que quisiéramos consultar, y si éste tuviera vinculado un script de nombre miPrimerScript -en caso contrario devolvería null- se ejecuta la función HazAlgunaCosa. Esta función nos permite además acceder a variables públicas (no private ni dentro de funciones) y funciones que se hallen en otro script que esté vinculado al mismo gameobject. Pongamos que el script del ejemplo lo vinculamos directamente a un gameobject (no como el anterior, en que arrastramos el gameobject a la variable expuesta) y dicho gameobject a su vez tiene vinculado otro script llamado otroScript. Podemos capturarlo con esta función y asignarlo a una variable de tipo ScriptName.
var unSegundoScript : ScriptName = gameObject.GetComponent(otroScript); // Una vez ya hemos capturado ese script, como decimos, podemos acceder a sus //funciones y variables públicas. unSegundoScript.HazOtraCosa (); unSegundoScript.tengoUnaVariable = 5; }
function GetComponent (name : String) : Component Es identica a la anterior, pero en lugar de acceder a un componente por su tipo, lo hacemos por su nombre a través de un string (aquí sí colocaremos comillas). Es preferible la primera firma por motivos de rendimiento, pero si no recordamos el tipo del componente, podemos buscar por el nombre. Recordemos que el nombre de un script ajeno pero vinculado al gameobject que hace la consulta es también su tipo, lo cual no obsta a que siga siendo su nombre y podamos consultarlo por él, aunque con comillas (“otroScript”, sería en este caso la manera de escribir el parámetro de búsqueda)
75. CLASE GAMEOBJECT (V)
GetComponentInChildren: function GetComponentInChildren (type : Type) : Component
Devuelve un componente activo de tipo que le pasamos como parámetro que pueda tener o bien el Gameobject que lanza el mensaje o bien sus hijos. La búsqueda empieza por los hijos.
GetComponents: function GetComponents (type : Type) : Component[]
Esta función devuelve todos los componentes del tipo Type y los devuelve en forma de array del tipo de componente solicitado. Esto nos permitiría hacer cosas como las del ejemplo que nos brinda el manual de referencia (no es preciso seguir el ejemplo, es sólo a modo oritentativo):
//Vamos a desconectar la variable spring de todos los HingeJoints que tenga este //gameobject. Primero crearemos un array de tipo HingeJoint y lo almacenamos //en la variable bisagras. var bisagras : HingeJoint[]; //Y colocamos todos los HingeJoint que encontremos en el array. bisagras = gameObject.GetComponents (HingeJoint); //Recorremos ahora en un bucle for in todos los componentes hallados y les //desactivamos la variable spring. for (var joint : HingeJoint in bisagras) { joint.useSpring = false; }
GetComponentsInChildren: function GetComponentsInChildren (type : Type, includeInactive : boolean = false) : Component[]
Devuelve todos los componentes del tipo pasado como primer parámetro que tenga ese gameobjects y sus hijos. La peculiaridad es que la función incluye un segundo parámetro opcional de tipo booleano que si establecemos en true nos permite recuperar también los componentes inactivos de ese tipo.
SetActiveRecursively:
function SetActiveRecursively (state : boolean) : void
Función que activa o desactiva el estado del gameobject que la invoca y de todos sus hijos, en función de si el bolean que tiene como parámetro está en true o false.
CompareTag: function CompareTag (tag : String) : boolean Esta función nos permite averiguar si un determinado gameobjet tiene como tag el string colocado como parámetro de la función. Si lo tiene, la función devuelve true. SendMessageUpwards: function SendMessageUpwards (methodName : String, value : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void Esta función llama a una función cuyo nombre pasamos en el parámetro methodName y que se encuentre en cualquier script que tengamos vinculado al gameobject que hace la llamada o en los "padres" de dicho gameobject. Le podemos, si procede, pasar un valor a dicho método (value). El parámetro options es del tipo SendMessageOptions, que es un enum con dos valores = requireReceiver (que necesita respuesta) y dontRequireReceiver, que no la requiere. Por defecto la función requiere respuesta, lo cual quiere decir que si se le pasa un argumento a la función receptora y ésta no precisa ninguno, imprimirá un mensaje de error. Si no lo requiriera (dontRequireReceiver), puede optar por ignorar dicho argumento sin más. Vamos a intentar aclararlo un poco más con un ejemplo. Previamente eliminemos el script que tenemos vinculado en PortaScripts. Acto seguido, editamos miPrimerScript y lo dejamos como sigue: function DameColor (tono : Color) { renderer.material.color= tono; } DameColor(Color.black);
La función no requiere más explicación. La arrastramos al cubo y al darle al play éste se torna de color negro. Ahora vamos a editar el script miSegundoScript, que si hemos seguido las lecciones debemos tener en Proyecto. (y si no creamos uno, tampoco nos volvamos locos). Tecleamos: function OnMouseEnter() { gameObject.SendMessageUpwards ("DameColor", Color.cyan); }
Arrastramos tras salvar este script también al cubo. De esta manera y a través de SendMessageUpwards podemos acceder a la función DameColor de miPrimersScript y usarla a nuestro gusto. En este caso nos limitamos a cambiar el color del cubo al pasar el ratón por éste, pero obviamente y en caso de gameobjects con un montón de scripts vinculados (que a eso llegaremos) esta posibilidad de acceder desde un scripts a las funciones que hay en otros es toda una ventaja. SendMessage: function SendMessage (methodName : String, value : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void Igual que la función anterior, con la diferencia de que en esta sólo podemos llamar a funciones que estén en scripts vinculados al gameobject, pero no en sus ancestros. BroadcastMessage: function BroadcastMessage (methodName : String, parameter : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void
Llama a un método con nombre methodName que esté en cualquier script vinculado a ese game object o a cualquiera de sus hijos, a diferencia de las funciones anteriores.
76. CLASE GAMEOBJECT (y VI)
AddComponent: function AddComponent (className : String) : Component
Añade un componente a nuestro gameobject. Ese componente ha de ser una instancia de la clase que ponemos en el parámetro className, o bien puede ser el nombre de un script. Es útil para añadir componentes en tiempo de ejecución. Algunos componentes requieren de la presencia de otros para existir, así que al añadir aquéllos automáticamente se nos añadirán estos. Pej, si añadimos un HingeJoint automáticamente se nos añadirá un Rigidbody.
function AddComponent (componentType : Type) : Component La única variación es que en lugar de string, introducimos el tipo de componente o el nombre del script sin comillas. Nótese que no existe la función RemoveComponent o similar. Para destruir un componente al vuelo, usad Object.Destroy
FUNCIONES DE CLASE: CreatePrimitive: static function CreatePrimitive (type : PrimitiveType) : GameObject
Crea un gameobject con una malla de tipo primitivo y el apropiado collider. Los tipos primitivos son sphere, capsule, cylinder, cube y plane. Veamos algún ejemplo (recordemos que es una función de clase, no vinculada a un objeto concreto) Eliminemos antes que nada los dos scripts vinculados al cubo. Y luego editamos miPrimerScript:
function Start() { var miCilindro : GameObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder); miCilindro.transform.position = Vector3(0,0,-2); }
Salvamos y arrastramos a PortaScripts. Vemos que hemos creado en ejecución un tipo primitivo (un cilindro en este caso) de entre los que la enum PrimitiveType nos permite), con su malla y su collider. Este
cilindro lo almacenamos en una variable de tipo Gameobject que a partir de ahí podemos tratar como un gameobject más, cambiándole, por ejemplo, de ubicación.
FindWithTag: static function FindWithTag (tag : String) : GameObject
Devuelve un gameobject con el tag que le pasamos a la función como string en su único parámetro. Si no existe ninguno. Devuelve null. Dado que es una función de clase y no va vinculada a ningún objeto, ha de quedar claro que el gameobject con el tag que buscamos no tiene por qué hacer referencia a ningún gameobject vinculado al script, sino a cualquier gameobject del juego que esté activo. El tag, eso sí, previamente debe estar declarado en el tag manager del inspector.
FindGameObjectWithTag: static function FindGameObjectsWithTag (tag : String) : GameObject[]
Esta función de clase devuelve una lista de gameobjects activos que tengan el tag requerido.
Find: static function Find (name : String) : GameObject
Busca un gameobject por nombre y lo devuelve. Aquí no buscamos por tag, sino por nombre. Hemos de reiterar que es una función de clase, no vinculada a un determinado gameobject, o sea, podemos buscar con esta función cualquier gameobject activo del juego.
Se puede usar el slash (/) para buscar a través de una jerarquía. Por motivos de rendimiento es aconsejable no usar esta función cada frame. Es mejor o, una vez hallado el gameobject, almacenarlo en una variable, o bien usar la función gameobject.findwithtag. Por poner un ejemplo sencillo, reeditemos una vez más miPrimerScript:
var buscaLaEsfera : GameObject; buscaLaEsfera = GameObject.Find("Esfera"); Debug.Log(buscaLaEsfera.name.ToString()); buscaLaEsfera.renderer.material.color = Color.green;
Tal como indicábamos en la definición, buscamos con Find un gameobject de nombre Esfera, lo almacenamos en una variable de tipo gameobject, y ya a partir de ahí podemos, por ejemplo, imprimir su nombre o cambiarle el color.
77. CLASE SHADER
Abordamos esta clase pequeña y poco importante más por seguir un orden en la jerarquía de clases que por otra cosa. No obstante, aunque será raro que nos topemos a menudo con la misma, no está de más tener una idea básica de su funcionalidad. Creo que ya indicamos en otro lugar que un shader es en 3d el algoritmo que especifica cómo una superficie reaccionará ante la luz. Muchos de los renders avanzados son controlados vía la clase Material. La clase Shader es más usada para comprobar si un shader puede correr en el hardware del usuario (propiedad isSupported) y para encontrar shaders por el nombre (método Find).
VARIABLES: isSupported: var isSupported : boolean
Bool de sólo lectura que indica si un determinado shader puede correr en la tarjeta gráfica del usuario. Se usa a veces cuando se implementan efectos especiales. Por ejemplo, image effects en Unity Pro automáticamente se deshabilitan si el shader no es soportado.
FUNCIONES DE CLASE: Find: static function Find (name : String) : Shader
Encuentra un shader con el nombre dado. Name es el nombre que vemos en el popup del shader de cualquier material. Son nombres comunes: "Diffuse", "Bumped Diffuse", "VertexLit", Transparent/Diffuse" etc.
78. CLASE PHYSICMATERIAL
Esta clase describe cómo manejar los objetos que chocan (fricción, capacidad de rebotar, etc). Es posible que recordemos cuando estudiábamos la clase Collider, que ésta tenía dos variables/atributos -material y sharedMaterial- de tipo PhysicMaterial. Por norma general para crear/modificar las capacidades de fricción y rebote de un gameobject, pues, lo haremos a través de la propiedad collider.material de dicho gameobject. VARIABLES: dynamicFriction: var dynamicFriction : float
La fricción usada cuando ya hay movimiento. Este valor ha de estar entre 0 y 1, siendo 0 la fricción del hielo y 1 la del caucho. Probemos un ejemplo. Para ello empecemos por eliminar el script vinculado a PortaScripts. Luego editamos miPrimerScript, de esta guisa:
collider.material.dynamicFriction = 0; function FixedUpdate() { rigidbody.AddForce(0,0,-9); }
Salvamos y le asignamos el script al cubo. Como vemos, le hemos dado al material la mínima fricción. Le damos al play y observamos que la fuerza arrastra el cubo sin problemas. Pero ahora probad a darle a dynamicFriction un valor de 0.4.
staticFriction: var staticFriction : float
La fricción usada cuando un objeto está reposando en una superficie. Usualmente un valor de 0 a 1. Siguiendo con nuestro ejemplo, podemos teclear:
collider.material.staticFriction = 0; collider.material.dynamicFriction = 0.2; function FixedUpdate() { rigidbody.AddForce(0,0,-9); }
Aquí nuestro cubo tendría una fricción en reposo nula y una en movimiento baja. Podemos tratar de invertir las tornas entre ambos valores e ir experimentando.
bounciness: var bounciness : float
Qué capacidad de rebote tiene la superficie. 0 es no rebote. Un valor de 1 implica rebote sin pérdida de energía. Para verlo con un ejemplo, eliminamos el script vinculado a nuestro cubo, primero, y luego colocamos la esfera en la posición (2,5,0). Editamos entonces miPrimerScript:
collider.material.bounciness = 0.7;
Lo vinculamos a la esfera. Observamos que ésta rebota un poco, sobre todo si comparamos el comportamiento de la esfera si establecemos esta propiedad en cero. No obstante, nos puede sorprender que a un valor tan alto como 0.7 la capacidad de rebote de nuestra esfera no sea superior. Dicha sorpresa se acrecienta si le asignamos el valor máximo (1) a bounciness. Lo que sucede es que Unity establece una media entre los valores de rebote de las dos superficies en contacto. Probad a arrastrar miPrimerScript también al Suelo y veréis la diferencia.
frictionDirection2: var frictionDirection2 : Vector3
Se refiere a la dirección de anisotropía. La fricción anisotrópica está habilitada salvo que su vector esté a cero. La anisotropía es aquella cualidad (forma, tamaño, temperatura, etc) que cambia en un objeto cuando se mueve. DynamicFriction2 y staticFriction2, que estudiaremos a continuación, serán aplicadas a lo lardo de frictionDirection2. La dirección de anisotropía es relativa al sistema de coordenadas local del collider. Utilizaremos un pequeño ejemplo para entender cómo funciona esto. Previamente devolvemos a la esfera al suelo (position.y=0) y luego eliminamos el script miPrimerScript que tenemos vinculado al suelo y la esfera. Editamos después de nuevo miPrimerScript:
collider.material.dynamicFriction = 1; collider.material.frictionDirection2 = Vector3.forward; collider.material.dynamicFriction2 = 0; function FixedUpdate() {
rigidbody.AddForce(5,0,0); }
Arrastramos tras salvar el script al cubo. Bien. Vamos a tratar de explicar qué tenemos aquí. En primer lugar establecemos la fricción dinámica del cubo al máximo, esto es, 1. De esta manera debería resultar difícil poderlo arrastrar. Pero, a continuación, le damos a frictionDirection2 un valor distinto a 0,0,0, esto es, lo activamos. Lo que le estamos diciendo aqui a Unity es que a lo largo del eje Z (forward) la fricción del cubo será distinta que cuando se mueva a lo largo de los otros dos ejes (esto es la anisotropía). Concretamente, en la tercera declaración le indicamos a Unity que la fricción dinámica en ese eje Z dynamicFriction2 será nula (0). En consecuencia, el resultado de este script debería hacer que el cubo se resistiera a moverse en los ejes X e Y pero en cambio no tuviera problemas en hacerlo en el eje Z. Para abrir boca, le aplicamos al script acto seguido una fuerza en el eje X. Pulsemos el play y comprobemos que el cubo si se mueve. Pero ahora cambiemos los parámetros de AddForce a (5,0-5), esto es, aplicándole la misma fuerza en el eje Z (que no debería tener resistencia) que al X. Si funciona como debiera, el cubo empezará a moverse hacia delante, pero seguirá negándose a hacerlo hacia la derecha, aunque la presión en un eje y otro es la misma. Probad.
dynamicFriction2: var dynamicFriction2 : float
Si la fricción anisotrópica está habilitada (frictionDirection2 no vale cero), dynamicFriction2 será aplicada a lo largo del eje/s de frictionDirection2 que no valga cero.
staticFriction2: var staticFriction2 : float
Si la fricción anisotrópica está habilitada, staticFriction2 será aplicada a lo largo de frictionDirection2.
frictionCombine: var frictionCombine : PhysicMaterialCombine Determina cómo se combina la fricción. Tal como explicaba hace unas líneas en relación con el rebote, asimismo las propiedades de fricción son dependientes de la combinación de los dos materiales en contacto. PhysicMaterialCombine (el tipo de este atributo) es una enumeración con las diferentes posibilidades de combinación de fricciones y capacidades de rebote.
Average: colliders. Multiply: colliders. Minimum: Maximum:
La media de la fricción o rebote de los materiales de los dos Multiplica la fricción o rebote de los materiales de los dos Usa el valor más bajo de fricción o rebote de los dos colliders. El valor más alto de los dos.
Así, la sintaxis para establecer la fricción entre dos colliders en el valor más alto de los dos sería así (a modo indicativo, nada más): collider.material.frictionCombine = PhysicMaterialCombine.Maximum; bounceCombine:
var bounceCombine : PhysicMaterialCombine
Determina cómo la capacidad de rebote es combinada. Al igual que la anterior, recurriremos al enum PysicMaterialCombine. FUNCIONES: PhysicMaterial: static function PhysicMaterial () : PhysicMaterial
La función constructora crea un nuevo material. Es normalmente más fácil, sin embargo, usar meramente un collider.material y modificar el material vinculado directamente. static function PhysicMaterial (name : String) : PhysicMaterial Crea un nuevo material con un nombre dado.
79. CLASE COMPONENT
Si realizamos un vistazo más o menos fugaz a las variables y funciones contenidas en esta clase, más de uno llegará a la conclusión de que guardan mucha similitud con las de la clase GameObject.
Tras compararlas más detenidamente, podemos hacer la siguiente afirmación: la clase Component no tiene ningún atributo ni propiedad que no tenga la clase GameObject. En aras de ser exactos, podemos decir que la clase Component es igual que la clase GameObject, sin las propiedades isStatic, layer y active y sin los métodos SetActiveRecursively, AddComponent, y obviamente su constructor. Esto es así porque Component no está pensada para ser usada directamente (para ello ya tenemos Gameobject) sino para que sus métodos y atributos sean heredados por buena parte de clases (de rigidbody a camera y de joint a light pasando por transform)
80. CLASE GUIELEMENT
Volvemos a bajar de nuevo en nuestra jerarquía de clases para buscar otra heredera de Behaviour. GUIElement es a su vez una clase base, que establece la funcionalidad mínima para todos los elementos de la GUI (Interfaz Gráfica de Usuario), entendiendo "elementos" como las imágenes y cadenas de texto mostradas en la GUI.
FUNCIONES: HitTest: function HitTest (screenPosition : Vector3, camera : Camera = null) : boolean Es un punto en la pantalla dentro del elemento. Devuelve true si la posición pasada como parámetro screenPosition está contenida en este GUIElement. La screenPosition es especificada en coordenadas de pantalla, a semejanza de los valores retornados por Input.mousePosition property. Si no se proporciona a la función una cámara en concreto, se asumirá por esta una cámara cubriendo la ventana del juego entera. Hemos de tener en cuenta que si la posición está dentro del elemento, esta función devolverá true incluso si el gameobject pertenece al layer Ignore Raycast.
GetScreenRect: function GetScreenRect (camera : Camera = null) : Rect
Devuelve lo bordes del rectángulo del GUIElement en coordenadas de pantalla. Si no se proporciona a la función una cámara en concreto, se asumirá por esta una cámara cubriendo la ventana del juego entera.
81. CLASE GUITEXT (I)
La clase GUIText es una de las dos que derivan de GUIElement (vista en la lección anterior). En concreto, la clase GUIText es la que se ocupa de los string de texto mostrados en un GUI (de las imágenes se ocupa GUITexture, que veremos luego).
VARIABLES: text: var text : String
Variable que contiene el texto que se muestra en el GUI. Empecemos con la habitual tanda de ejemplos. Para empezar, eliminamos el script que tenemos en el cubo. Luego vamos a crear un gameobject de tipo GUIText, para lo que nos vamos al menú=>Gameobject=>Create other=>Gui Text. Renombramos a nuestro nuevo gameobject como "miTexto". Observaremos que en la ventana Game nos aparece un texto ("gui text") por defecto.
Editamos miPrimerScript:
guiText.text = "Bienvenidos a unityscripts";
Arrastramos tras salvar el script a miTexto. Play. Observamos que el texto que hemos introducido se nos muestra en la ventana Game, tal como se ve en esta captura:
material: var material : Material
El material usado para renderizar el texto. Esta variable nos permite acceder a él para modificarlo o crear uno. Si le damos un valor null se mostrará la fuente por defecto. Podemos por ejemplo completar un poco más nuestro anterior script:
guiText.text = "Bienvenidos a unityscripts"; guiText.material.color = Color.magenta;
Y habremos alterado el color de nuestras letras de bienvenida.
pixelOffset: var pixelOffset : Vector2
El desplazamiento en píxeles del texto desde su posición inicial (marcada por su transform) en base a los valores contenidos en un Vector2. Un ejemplo sencillo que no merece más comentario:
guiText.text = "Bienvenidos a unityscripts";
guiText.material.color = Color.magenta; guiText.pixelOffset = Vector2 (-150, 100);
82. CLASE GUITEXT (y II)
font: var font : Font
La fuente usada para el texto. Podemos asignar una de entre las que tengamos disponibles.
alignment: var alignment : TextAlignment
La alineación del texto. TextAlignment es una estructura con los valores Left, Center y Right. Para probar esto hemos de añadir más texto a nuestro ejemplo del capítulo con algunos saltos de linea:
guiText.text = "Bienvenidos a unityscripts. \nAñadimos una \nsegunda y tercera linea"; guiText.material.color = Color.magenta; guiText.alignment = TextAlignment.Center;
Podemos comprobar que el texto se alinea centrado.
anchor: var anchor : TextAnchor
El ancla del texto. TextAnchor es una enumeración que permite indicar dónde se colocará el ancla o fijación del texto. Tiene estas posibilidades:
UpperLeft: El texto se fija en la esquina superior izquierda. Para entendernos, la parte superior de la B de nuestro "Bienvenidos" del ejemplo estará ubicada en el punto marcado por las coordenadas de posición del transform del GUIText.
UpperCenter: El texto se fija en el lado central superior, esto es, la mitad de nuestra frase, medida horizontalmente, en su lado superior, coincidirá con la ubicación marcada por el transform. UpperRight: El texto se fija en la esquina superior derecha. MiddleLeft: El texto se fija en el lado izquierdo, centrado verticalmente. MiddleCenter:El texto se centra tanto vertical como horizontalmente respecto de su transform. MiddleRight: El texto se ancla en el lado derecho, centrado verticalmente. LowerLeft: El texto se fija en la esquina inferior izquierda. LowerCenter: El texto se ancla en la parte inferior, centrada horizontalmente. LowerRight:
El texto se fija en la esquina inferior derecha.
Vamos a centrar nuestra frase respecto la posición del transform del GUIText. Añadimos al script:
guiText.text = "Bienvenidos a unityscripts. \nAñadimos una \nsegunda y tercera linea"; guiText.material.color = Color.magenta; guiText.alignment = TextAlignment.Center; guiText.anchor = TextAnchor.MiddleCenter;
Ahi lo tenemos.
lineSpacing: var lineSpacing : float
El multiplicador del espacio de interlineado. Esta cantidad será multiplicada por el espacio de línea definido en la fuente.
tabSize: var tabSize : float
El multiplicador de la anchura del tab. Esta cantidad se multiplicará con la anchura de tab definida en la fuente.
fontSize: var fontSize : int
El tamaño de fuente a usar (para fuentes dinámicas). Si lo establecemos en una cantidad distinta de cero, el tamaño de fuente especificado en la fuente importada es sobreescrito con un tamaño personalizado. Esto sólo es soportado para fuentes que usen fuentes dinámicas de renderizado. Otras fuentes siempre usarán el tamaño de fuente por defecto.
fontStyle: var fontStyle : FontStyle
Lo mismo que la anterior para el estilo de Fuentes dinámicas.
83. CLASE GUITEXTURE
Clase "hermana" de GUIText, GUITexture se encarga de manejar las imágenes que compondrán nuestra GUI en 2d. Para una mejor comprensión, no tenéis más que hacer lo siguiente: eliminad el gameobject "miTexto" y acto seguido ir al menú=>gameobject=>create other=>GUI texture. Aparecerá por defecto el icono de Unity en nuestra escena. Llamemos a nuestro nuevo gameobject "logoUnity".
VARIABLES: color: var color : Color
El color de la textura de la GUI.
Un ejemplo muy sencillo. Editamos miPrimerScipt:
guiTexture.color = Color.blue;
Lo arrastramos a logoUnity, play, y nuestra imagen/textura pasa a ser de color azul.
texture: var texture : Texture
La textura usada para dibujar. Es posible que conservemos todavía en Proyecto la imagen que llamamos "multicolor" que usamos para que diera vueltas alrededor de la esfera. Si la tenéis ahí, perfecto, y si no arrastrad hasta la carpeta assets donde tengáis guardado vuestro proyecto cualquier imagen. Luego editamos miPrimerScript como sigue:
var unaTextura : Texture; guiTexture.texture = unaTextura;
Salvamos y arrastramos la textura a la variable expuesta. Al darle al play observamos que la misma sustituye al logo de Unity.
pixelInset: var pixelInset : Rect
Inserción de pixels usada para ajustar píxeles para tamaño y posión. Pueder poner el transform.localScale a Vector3.zero para hacer que la GUI texture siempre tenga el mismo tamaño de píxeles. Reeditemos una vez más nuestro script:
transform.position = Vector3.zero; transform.localScale = Vector3.zero; guiTexture.pixelInset = Rect (100, 25, 180, 180);
Antes de darle al play expliquemos lo que hemos hecho. Primero colocamos el transform de nuestra GUITexture en el centro de la escena. Acto seguido colocamos la escala del transform a cero para que tenga el tamaño que posteriormente le indiquemos, sin ningún tipo de variación. Acto seguido encuadramos la textura dentro de un rectángulo situado a 100 píxeles desde la izquierda y 25 desde arriba, con una anchura y altura de 180 píxeles. Dadle al play.
84. CLASE GUI (I)
Bueno. Hasta ahora nos hemos centrado en estudiar las clases que conformaban el árbol de herencia que ocupa buena parte de la API de Unity. Hemos ido repasándolas (casi) todas, más o menos en orden. A partir de ahora, en cambio, vamos a recorrer clases que no están vinculadas por relación de herencia alguna con las ya explicadas (salvo alguna excepción, que anunciaremos como tal cuando corresponda). Por tanto la cuestión del orden en su estudio obedecerá a criterios un poco más liviamos, como su mayor o menor importancia o su vinculación funcional con la clase que se haya estudiado con anterioridad. En base a ese último criterio, y dado que las tres últimas clases estaban relacionadas con la inferfaz gráfica de usuario, vamos a dedicar las siguientes lecciones a darle unas vueltas a diferentes clases que de una manera u otra están vinculadas con la GUI. Y, como no podía ser de otra forma, empezamos por la clase GUI, que representa la interfaz de Unity:
VARIABLES DE CLASE: skin: static var skin : GUISkin
La skin (que podemos traducir por "piel" o "aspecto") en uso. Cambiando esta variable podemos cambiar el look de nuestra GUI. Si su valor es null, se muestra la skin que está por defecto. Es una instancia de la clase GUISkin, que estudiaremos a continuación de ésta.
color: static var color : Color
Color del tintero global de la GUI. Afectará tanto a la parte trasera de los elementos (background) como a los colores del texto que escribamos sobre cualquier superficie. Vamos con el primer ejemplo. Empezamos por eliminar el objeto logoUnity. A continuación editamos miPrimerScript:
function OnGUI() { GUI.color = Color.yellow; GUI.Label (Rect (10, 10, 200, 20), "Esto es una etiqueta"); GUI.Box(Rect(10, 50, 100, 50), "Una caja"); GUI.Button(Rect(10,110,90,50), "Un botón"); }
Salvamos y la arrastramos a PortaScripts. Si pulsamos play observaremos algo como esto:
Aquรญ hemos de explicar varias cosas. Empezando por el final, comprobamos en la imagen que el texto de la GUI, sobre las diferentes superficies, es del color que le hemos indicado a la variable color. Asimismo, los bordes de elementos como el botรณn adquieren en su parte posterior (background) ese color amarillo. Esa declaraciรณn y las anteriores estรกn contenidas dentro de una funciรณn que vimos un poco de puntillas cuando estudiamos la clase MonoBehaviour: la funciรณn OnGUI renderiza y maneja eventos GUI. Dicho de otra manera, al llamar a esta funciรณn activamos un evento GUI, que en este caso asigna un color a la letra y luego crea una etiqueta, una caja y un botรณn. backgroundColor: static var backgroundColor : Color Color del tintero para todas las partes traseras (background) de los elementos renderizados para la GUI. Dicho de otra manera, esta variable de clase hace parte del trabajo que efectuaba color, ya que colorea el background pero no el texto. Reeditamos miPrimerScript como sigue:
function OnGUI() { GUI.backgroundColor = Color.red; GUI.Button(Rect(10,10,70,30), "Mi botรณn"); } Al darle al play podemos observar que los bordes del botรณn que hemos creado son de color rojo, color que se acrecienta cuando colocamos el ratรณn encima. En cambio, el color del texto sigue siendo blanco (el color por defecto) contentColor: static var contentColor : Color Color de tinta para todo el texto renderizado en la GUI. Esta funciรณn es la complementaria de la anterior con relaciรณn a color, ya que no afecta al color del background, sino al del texto. Por ejemplo:
function OnGUI() { GUI.contentColor = Color.yellow; GUI.backgroundColor = Color.red;
GUI.Button(Rect(10,10,70,30), "Mi botón"); }
Vemos aquí claramente las diferencias entre ContentColor (amarillo) y backgroundColor (rojo).
85. CLASE GUI ( II)
changed: static var changed : boolean
Devuelve true si algún control cambia el valor de los datos de entrada de la GUI. Podemos aprovechar el ejemplo que aparece en el manual de referencia para ilustrarnos. Editamos miPrimerScript como sigue:
var miTexto : String = "Cambiame"; function OnGUI () { miTexto = GUI.TextField (Rect (10, 10, 200, 20), miTexto, 25); if (GUI.changed) Debug.Log("El campo de texto se modificó"); }
El contenido del script es bastante intuitivo: creamos un campo de texto editable con la función TextField, que en breve estudiaremos, y, si procedemos a cambiar su contenido inicial se nos imprimirá en pantalla un texto avisándonos de dicha modificación.
enabled: static var enabled : boolean Habilita/deshabilita la GUI. Si establecemos esta variable en false se deshabilitan todas las interacciones de la GUI. Todos los controles se dibujarán semitransparentes, y no responerán a las entradas del usuario.
tooltip: static var tooltip : String
Un tooltip es ese pequeña nota emergente que aparece a veces con determinada información cuando colocamos un ratón sobre un control, o dicho control tiene el foco del teclado. Vamos con el pertinente ejemplo. Abrimos nuestro script y:
function OnGUI () {
GUI.Button (Rect (10,10,100,20), GUIContent ("Pulsame", "Este es el tooltip")); GUI.Label (Rect (10,40,100,40), GUI.tooltip); }
El script funciona de la siguiente manera: Como viene siendo habitual, empezamos creando un evento GUI con la función OnGUI. Acto seguido creamos un botón con una determinada ubicación y dimensiones, y le pasamos como segundo parámetro la función constructora de la clase GUIContent (de cercano estudio), que a su vez admite como parámetros el texto del botón, una imagen (en este caso no) y en su caso el texto del tooltip que se deba activar al pasarle el ratón por encima. Acto seguido hemos de crear propiamente la etiqueta del tooltip, indicando su ubicación y dimensiones. Al darle al play y colocar el ratón sobre el botón, automáticamente nos aparecerá un tooltip con el texto indicado, que desaparecerá al retirar el ratón de dicho control.
depth: static var depth : int
El orden de profundidad que tendrá cada actividad GUI en ejecución. Quiere esto decir que cuando tengamos varios scripts ejecutándose simultáneamente, los elementos GUI que tengan valores de profundidad más bajos aparecerán en la pantalla encima de los que lo tengan más altos
86. CLASE GUI (III)
FUNCIONES DE CLASE: label: static function Label (position : Rect, text : String) : void static function Label (position : Rect, image : Texture) : void static function Label (position : Rect, content : GUIContent) : void static function Label (position : Rect, text : String, style : GUIStyle) : void static function Label (position : Rect, image : Texture, style : GUIStyle) : void static function Label (position : Rect, content : GUIContent, style : GUIStyle) : void Crea una etiqueta (label) de texto o de imagen en la pantalla. Como podemos observar, esta función tiene varios prototipos, permitiéndonos pasarle distintos parámetros en base a la necesidad y datos que tengamos en cada momento. Dichos parámetros serían:
position: Rectangulo en la pantalla a usar para la etiqueta. text: Texto a mostrar en la etiqueta. image: Textura/imagen a mostrar en la etiqueta. content: Texto, imagen y tooltip para esta etiqueta. style: El estilo a usar. Si no se indica, se aplicará el estilo para etiquetas que tenga el GUISking que se esté usando.
En base al significado de estos parámetros, podemos fácilmente deducir la diferencia entre los distintos prototipos de esta función.
Las etiquetas o labels en sí no implican ningún tipo de interacción con el usuario, no captan clicks del ratón y son siempre renderizadas en un estilo normal (no por ejemplo como los botones, que aparte del estilo normal tienen otro para cuando se pasa el ratón por encima, otro cuando se presionan, o cuando se activan, etc). Pongamos el más sencillo de los ejemplos: mostremos en pantalla el famoso hello world.
function OnGUI () { GUI.Label (Rect (10, 10, 100, 20), "Hello World"); }
Como vemos, hemos optado por el primer prototipo de la función de los que mostramos al inicio. Meramente el rectángulo con la posición (10,10) y tamaño (100,20) del rectángulo donde ubicaremos la etiqueta, y como segundo parámetro un string con el texto. Si en lugar de mostrar un texto quisiéramos hacer lo propio con una imagen, modificaríamos la función como sigue:
var unaTextura : Texture2D; function OnGUI () { GUI.Label (Rect (10, 40, unaTextura.width, unaTextura.height), unaTextura); }
Salvamos y arrastramos la textura que teníamos en la carpeta assets de ejemplos anteriores a la variable expuesta de este script que tenemos vinculado a PortaScripts. En este prototipo de nuevo pasamos como parámetro primero un rectángulo con la posición del rectángulo (10,40) y, en lugar de indicar directamente las dimensiones que ha de tener dicho rectángulo, aprovechamos la anchura y altura originales de la imagen arrastrada para no recortarla (aunque por supuesto podemos sentirnos libres de establecer unas dimensiones fijas). El segundo parámetro es donde difiere este prototipo del anterior, ya que en lugar de suministrar un string pasamos una textura.
DrawTexture: static function DrawTexture (position : Rect, image : Texture, scaleMode : ScaleMode = ScaleMode.StretchToFill, alphaBlend : boolean = true, imageAspect : float = 0) : void
Dibuja una textura dentro de un rectángulo. Tiene los siguientes parámetros:
position: Rectángulo en la pantalla para dibujar la textura dentro. image: Textura a mostrar. scaleMode: Cómo escalar la imagen cuando la proporción hace que no encaje bien dentro del rectángulo. alphaBlend: Si el canal alfa es mezclado en la imagen (por defecto) imageAspect: Proporción a usar para la imagen fuente. Si vale 0 (por defecto), es usada la proporción de la imagen. Pasad un w/h para la deseada proporción, lo cual permite cambiar la proporción de la imagen original sin cambier la anchura y altura de píxeles.
El parámetro scaleMode es a su vez una enumeración que acepta los siguientes valores:
StretchToFill: Estira la textura para rellenar el rectangulo entero. ScaleAndCrop: Escala la textura, manteniendo la proporción, hasta cubrir completamente el rectángulo. Si la textura se dibuja en un rectángulo con una proporción diferente, la imagen se recorta. ScaleToFit: Escala la textura, manteniendo la proporción, hasta encajar completamente dentro del rectángulo.
Vamos ahora a dibujar una textura en la esquina izquierda de la pantalla, textura que se dibujará en una ventana de 60 x 60 píxeles. A la textura original le daremos una proporción de 10 x 1 y la haremos encajar luego en el rectángulo anterior con el Scalemode.ScaleToFit, de tal manera que la textura se escalará hasta encajar horizontalmente con el rectángulo, manteniendo la proporción de 10/1.
function OnGUI() { } GUI.DrawTexture(Rect(10,10,60,60), aTexture, ScaleMode.ScaleToFit, true, 10.0f); }
Box:
static function Box (position : Rect, text : String) : void static function Box (position : Rect, image : Texture) : void static function Box (position : Rect, content : GUIContent) : void static function Box (position : Rect, text : String, style : GUIStyle) : void static function Box (position : Rect, image : Texture, style : GUIStyle) : void static function Box (position : Rect, content : GUIContent, style : GUIStyle) : void
Como su nombre indica, crea un cuadro o caja. Sus diferentes prototipos cuentan con estos parámetros:
position: text: image: content: style: caja
Rectángulo en la pantalla a usar para la caja. Texto a mostrar en la caja. Textura a mostrar en la caja. Texto, imagen y tooltip para la caja. El estilo a usar. Si no se indica expresamente, el estilo de la será el del GUISkin en uso.
Pongamos un ejemplo sencillo:
function OnGUI() { GUI.Box(Rect(10,20,100,40),"Hola, mundo"); }
87. CLASE GUI (IV)
Button: static function Button (position : Rect, text : String) : boolean static function Button (position : Rect, image : Texture) : boolean static function Button (position : Rect, content : GUIContent) : boolean static function Button (position : Rect, text : String, style : GUIStyle) : boolean static function Button (position : Rect, image : Texture, style : GUIStyle) : boolean static function Button (position : Rect, content : GUIContent, style : GUIStyle) : boolean Función que crea un botón que, al ser pulsado por un usuario, dispara algún tipo de evento. Una cosa que puede llamar la atención inicialmente, y que tiene mucho que ver con la manera un poco atípica de usar esta función, es que retorna un booleano que es establecido en true cuando el usuario hace click sobre el botón. De esta suerte, la función devolverá false hasta que se pulse el botón, lo que implica en la práctica que esta función se suele usar tras una declaración if, como enseguida veremos. Antes detengámonos un momento en los parámetros, que ya nos deberían sonar:
position: text: image: content: style:
Rectangulo en la pantalla a usar para el botón. Texto a mostrar en el botón. Textura/imagen a mostrar en el botón. Texto, imagen y tooltip para este botón. El estilo a usar. Si no se indica, se aplicará el estilo para botones que tenga el GUISking que se esté usando.
Y ahora es el momento de entender con un ejemplo lo que os comentaba antes sobre la peculiar manera de usar la función button:
var unaTextura : Texture; function OnGUI() { if (!unaTextura) { Debug.LogError("Asigne por favor una textura en el inspector"); return; } if (GUI.Button(Rect(10,10,50,50),unaTextura)) Debug.Log("Has hecho click en el botón que tiene una imagen"); if (GUI.Button(Rect(10,70,50,30),"Click")) Debug.Log("Has hecho click en el botón con texto"); }
Vamos por partes. Salvamos el script y, sin arrastrar ninguna textura a la variable expuesta que nos debería aparecer en el inspector al tener PortaScripts seleccionado, pulsamos play. Nos aparecerá un mensaje de error avisándonos de que debemos arrastrar dicha textura. Es esta una variande de Debug.Log llamada Debug.LogError, que hace que el mensaje aparezca en rojo. Detenemos el reproductor, arrastramos la textura y volvemos a pulsar play. Observaremos que nos aparecen dos botones, uno con la textura arrastrada por nosotros, otros con el texto indicado, y que al
pulsarlos aparecen en pantalla sendos textos. Lo que personalmente me parece más interesante de todo esto es la manera de utilizar la función:
if (GUI.Button(Rect(10,70,50,30),"Click"))
Pensemos que con esta declaración estamos haciendo dos cosas: primero, al pasarla como condición de if nos aseguramos de que una vez el user clicke el botón y por ende la función Button devuelva true, se ejecutará las declaraciones que se vean afectadas por ese if. Pero, además, previo a devolver true o false, Unity ejecuta la función, y por consiguiente se renderiza el botón en la pantalla.
RepeatButton: static function RepeatButton (position : Rect, text : String) : boolean static function RepeatButton (position : Rect, image : Texture) : boolean static function RepeatButton (position : Rect, content : GUIContent) : boolean static function RepeatButton (position : Rect, text : String, style : GUIStyle) : boolean static function RepeatButton (position : Rect, image : Texture, style : GUIStyle) : boolean static function RepeatButton (position : Rect, content : GUIContent, style : GUIStyle) : boolean
Función que crea un botón que está activo mientras el usuario lo presiona.
TextField:
static function TextField (position : Rect, text : String) : String static function TextField (position : Rect, text : String, maxLength : int) : String static function TextField (position : Rect, text : String, style : GUIStyle) : String static function TextField (position : Rect, text : String, maxLength : int, style : GUIStyle) : String Crea un campo de texto de una línea donde el usuario puede editar un string. Devuelve un string con el texto editado. Tiene estos parámetros:
position: Rectángulo en la pantalla a usar para el campo de texto. text: Texto a editar. El valor de retorno debe ser reasignado de vuelta al string tal como se enseña en el próximo ejemplo. maxLength: La longitud máxima del string. Si no se indica, el usuario puede escribir sin ningún tipo de límite. style: El estilo a usar. Si no se indica, se aplicará el estilo para textField que tenga el GUISkin en uso.
Esta función es importante porque permite una mayor interactividad con el usuario que meramente pulsar determinamos botones. Por ello es importante que el texto que dicho usuario introduce se almacene debidamente. Tal como indica el manual de referencia al hablar del parámetro text, la solución ideal es inicializar una variable string con el valor que le damos por defecto, y reutilizar dicha variable para posteriormente contener el string ya modificado y devuelto por la función. En definitiva:
var mensaje : String = "Escribe algo"; function OnGUI () { mensaje = GUI.TextField (Rect (10, 10, 200, 20), mensaje, 25); Debug.Log(mensaje); }
Como veis, la idea es que "mensaje" sirva tanto para contener el string inicial como el modificado. Le he añadido a continuación un Debug.Log para que comprobéis en tiempo real cómo va cambiando el contenido de la variable.
PasswordField: static function PasswordField (position : Rect, password : String, maskChar : char) : String static function PasswordField (position : Rect, password : String, maskChar : char, maxLength : int) : String static function PasswordField (position : Rect, password : String, maskChar : char, style : GUIStyle) : String static function PasswordField (position : Rect, password : String, maskChar : char, maxLength : int, style : GUIStyle) : String Crea un campo de texto donde el usuario puede introducir una contraseña. Devuelve un string con la contraseña editada. Cuenta con los siguientes parámetros:
position: Rectángulo en la pantalla a usar para el campo de texto. password: Contraseña a editar. El valor de retorno de esta función debe ser reasignado al string que contenía el valor original. maskChar: El carácter con el que queremos enmascarar la contraseña. maxLength: La longitud máxima del string. Si no se indica, el usuario no tendrá límite para escribir. style: El estilo a usar. Si no se indica, se usará el estilo para textfield que tenga el GUISkin que se esté usando.
Podemos apreciar que en definitiva esta función es como la anterior, con la salvedad de que en la presente le añadimos un carácter (tradicionalmente un asterisco) que queremos que aparezca en pantalla cuando el usuario teclee su contraseña.
var miPin : String = ""; function OnGUI () { miPin = GUI.PasswordField (Rect (10, 10, 200, 20), miPin, "*"[0], 25); }
88. CLASE GUI (V)
TextArea: static function TextArea (position : Rect, text : String) : String static function TextArea (position : Rect, text : String, maxLength : int) : String static function TextArea (position : Rect, text : String, style : GUIStyle) : String static function TextArea (position : Rect, text : String, maxLength : int, style : GUIStyle) : String Crea un área de texto de varias líneas donde el usuario puede editar un string. Devuelve el string editado.
SetNextControlName: static function SetNextControlName (name : String) : void
Función que establece el nombre del siguiente control. Esto hace que el siguiente control sea registrado con un nombre dado.
GetNameOfFocusedControl: static function GetNameOfFocusedControl () : String
Obtiene el nombre del control que tiene el foco. El nombre de los controles es asignado usando SetNextControlName. Cuando un control tiene el foco, esta función devuelve su nombre. GetNameOfFocusedControl funciona especialmente bien cuando tratamos con ventanas para loguearse.
FocusControl: static function FocusControl (name : String) : void
Mueve el foco del teclado a un control nombrado.
Toggle: static function Toggle (position : Rect, value : boolean, text : String) : boolean static function Toggle (position : Rect, value : boolean, image : Texture) : boolean static function Toggle (position : Rect, value : boolean, content : GUIContent) : boolean static function Toggle (position : Rect, value : boolean, text : String, style : GUIStyle) : boolean static function Toggle (position : Rect, value : boolean, image : Texture, style : GUIStyle) : boolean static function Toggle (position : Rect, value : boolean, content : GUIContent, style : GUIStyle) : boolean Crea un botón de tipo interruptor (on/off). Devuelve el nuevo valor del botón(true/false).
Toolbar: static function Toolbar (position : Rect, selected : int, texts : string[]) : int static function Toolbar (position : Rect, selected : int, images : Texture[]) : int static function Toolbar (position : Rect, selected : int, content : GUIContent[]) : int static function Toolbar (position : Rect, selected : int, texts : string[], style : GUIStyle) : int static function Toolbar (position : Rect, selected : int, images : Texture[], style : GUIStyle) : int static function Toolbar (position : Rect, selected : int, contents : GUIContent[], style : GUIStyle) : int
Función que crea una barra de herramientas. Devuelve -un int- el índice de la toolbar seleccionado. Tiene estos parámetros:
position: selected: texts: images: contents: toolbar. style: que
Rectángulo en la pantalla a usar para la barra de herramientas. El índice del botón seleccionado. Un array de strings a enseñar en los botones de la barra. Un array de textras para los botones de la barra de herramientas. Un array de texto, imágenes y tooltips para los botones del El estilo a usar. Si no se indica, se usará el estilo para botones tenga la GUISkin que se esté usando.
Veámoslo con un ejemplo:
var toolbarIndice : int = 0; var toolbarStrings : String[] = ["Toolbar1", "Toolbar2", "Toolbar3"]; function OnGUI () { toolbarIndice = GUI.Toolbar (Rect (25, 25, 250, 30), toolbarIndice, toolbarStrings); Debug.Log("El índice pulsado es " + toolbarIndice); }
Observaremos que nos aparece en pantalla al pulsar play una barra con tres botones, estando por defecto activado el primero, que corresponde con el índice cero que le hemos pasado como parámetro. Le hemos añadido la función Debug.Log para acreditar que al presionar los distintos botones se le asigna a la variable el índice de cada botón.
89. CLASE GUI (VI)
SelectionGrid: static function SelectionGrid (position : Rect, selected : int, texts : string[], xCount : int) : int static function SelectionGrid (position : Rect, selected : int, images : Texture[], xCount : int) : int static function SelectionGrid (position : Rect, selected : int, content : GUIContent[], xCount : int) : int static function SelectionGrid (position : Rect, selected : int, texts : string[], xCount : int, style : GUIStyle) : int static function SelectionGrid (position : Rect, selected : int, images : Texture[], xCount : int, style : GUIStyle) : int static function SelectionGrid (position : Rect, selected : int, contents : GUIContent[], xCount : int, style : GUIStyle) : int
Hace una cuadrícula (grid) de botones. Devuelve un int con el índice del botón seleccionado. Permite los siguientes parámetros:
position: selected: texts: images: contents cuadrícula xCount: serán
Rectándulo de la pandalla a usar para la cuadrícula. El índice del botón seleccionado de la cuadrícula. Un array de strings que mostrar en los botones de la cuadrícula. Un array de texturas en los botones de la cuadrícula. Un array de texto, imágenes y tooltips para los botones de la Cuántos elementos caben en la dirección horizontal. Los controles escalados para encajar a meno que el estilo elegido para usar sea
style: marcado
fixedWidth. El estilo a usar. Si no se indica, se usa el estilo de botón por el GUISkin que se esté usando.
Veamos un breve ejemplo:
var selGridInt : int = 0; var selStrings : String[] = ["Grid 1", "Grid 2", "Grid 3", "Grid 4"]; function OnGUI () { selGridInt = GUI.SelectionGrid (Rect (25, 25, 100, 30), selGridInt, selStrings, 2); }
HorizontalSlider: static function HorizontalSlider (position : Rect, value : float, leftValue : float, rightValue : float) : float static function HorizontalSlider (position : Rect, value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb : GUIStyle) : float Crea una barra de desplazamiento horizontal que el usuario puede arrastrar para cambiar un valor entre un mínimo y un máximo. Devuelve un float con el valor que ha sido elegido por el usuario. Parámetros:
position: value: leftValue: rightValue slider: thumb: usa,
Rectángulo en la pantalla a usar para la barra. El valor que muestra la barra. Esto determina la posición del deslizable móvil. El valor del extremo izquierdo de la barra. El valor del extremo derecho de la barra. El GUIStyle a usar para mostrar el área de arrastre. Si no se utiliza, se usará el estilo de horizontalSlider que tenga por defecto el GUISkin que se esté usando. El GUIStyle a usar para mostrar el deslizable móvil. Si no se se usará el estilo de horizontalSliderThumb style que tenga por defecto el GUISkin que se esté usando.
Y por último el ejemplo:
var valorDelSlider : float = 0.0; function OnGUI () { valorDelSlider = GUI.HorizontalSlider (Rect (25, 25, 100, 30), valorDelSlider, 0.0, 10.0); }
VerticalSlider: static function VerticalSlider (position : Rect, value : float, topValue : float, bottomValue : float) : float static function VerticalSlider (position : Rect, value : float, topValue : float, bottomValue : float, slider : GUIStyle, thumb : GUIStyle) : float
Crea una barra de deslizamiento vertical que el usuario puede arrastrar para cambiar un valor entre un mínimo y un máximo. Devuelve un float con el valor que ha sido escogido por el usuario.
Tiene los siguientes parámetros:
position: value:
Rectángulo en la pantalla a usar para la barra. El valor que muestra la barra. Esto determina la posición del deslizable móvil. El valor en lo alto de la barra El valor en lo bajo de la barra El GUIStyle a usar para mostrar el área de arrastre. Si no se utiliza, se usará el estilo de verticalSider que tenga por defecto el GUISkin que se esté usando. El GUIStyle a usar para mostrar el deslizable móvil. Si no se
topValue: bottomValue: slider: thumb: usa,
se usará el estilo de verticalSliderThumb que tenga por defecto el GUISkin que se esté usando.
HorizontalScrollbar:
static function HorizontalScrollbar (position : Rect, value : float, size : float, leftValue : float, rightValue : float) : float static function HorizontalScrollbar (position : Rect, value : float, size : float, leftValue : float, rightValue : float, style : GUIStyle) : float
Crea una barra de desplazamiento (scrollbar) horizontal. Un scrollbar es lo que usamos para desplazarnos por un documento. Por norma general en lugar de scrollbar usaremos scrolliews. Devuelve un float con el valor modificado. Este puede ser cambiado por el usuario arrastrando el scrollbar o clickando en las flechas de los extremos.
Un breve ejemplo:
var valorDeBarra : float; function OnGUI () { valorDeBarra = GUI.HorizontalScrollbar (Rect (25, 25, 100, 30), valorDeBarra, 1.0, 0.0, 10.0); }
VerticalScrollbar: static function VerticalScrollbar (position : Rect, value : float, size : float, topValue : float, bottomValue : float, style : GUIStyle) : float
Crea una barra de desplazamiento (scrollbar) vertical.
90. CLASE GUI ( VII)
BeginGroup: static function BeginGroup (position : Rect) : void static function BeginGroup (position : Rect, text : String) : void static function BeginGroup (position : Rect, image : Texture) : void static function BeginGroup (position : Rect, content : GUIContent) : void static function BeginGroup (position : Rect, style : GUIStyle) : void static function BeginGroup (position : Rect, text : String, style : GUIStyle) : void static function BeginGroup (position : Rect, image : Texture, style : GUIStyle) : void static function BeginGroup (position : Rect, content : GUIContent, style : GUIStyle) : void
Comienza un grupo. Esta función debe emparejarse con una llamada a EndGroup. Cuando comenzamos un grupo, el sistema de coordenadas para los controles GUI será tal que coincidirá la coordenada (0,0) con la esquna superior izquierda del grupo. Los grupos pueden ser anidados, estando lo hijos agrupados respecto de sus padres. Esto es muy útil cuando movemos un montón de elementos GUI a lo largo de la pantalla. Un caso de uso común es diseñar nuestros menús para que encajen en un específico tamaño de pantalla, centrando la GUI en pantallas más amplias. Los distintos prototipos de función usan estos parámetros:
position: text: image: content:
Rectángulo en la pantalla a usar para el grupo. Texto a mostrar en el grupo. Textura a mostrar en el grupo. Texto, imagen y tooltip para este grupo. Si el parámetro es proporcionado, cualquier click de ratón es capturado por el
grupo y si no se proporciona, no se renderiza ingún bacground y los clicks style:
del ratón son renderizados. El estilo a usar para el background.
Veremos muy claramente la funcionalidad de este método con un ejemplo:
function OnGUI () { GUI.BeginGroup (new Rect (Screen.width / 2 -200 , Screen.height / 2 - 150, 400, 300)); GUI.Box (new Rect (0,0,400,300), "Este cuadrado está ahora centrado, y dentro del mismo podemos colocar nuestro menú"); GUI.EndGroup (); }
Expliquemos páso a paso en qué consiste lo que hemos hecho. Para empezar usamos la función BeginGroup para crear un grupo en un rectángulo que se iniciará en el centro de la pantalla. Si lo ubicáramos en width/2 el rectángulo quedaría desplazado, pues no se estaría teniendo en cuenta en este caso la anchura del propio rectángulo. De esta manera, le hemos de restar al centro de la pantalla la
mitad de la anchura del rectángulo, asegurándonos así que queda justo en el centro. Hacemos lo propio con la altura. Una vez ya tenemos definido para el grupo un rectángulo centrado con unas dimensiones de 400 X 300, ahora para los controles dentro de dicho grupo la esquina superior izquierda del rectángulo pasa a ser la coordenada 0,0. Así, cuando a continuación invocamos una caja con un texto y la ubicamos en las coordenadas 0,0, ésta se nos viene a colocar al inicio del rectángulo del grupo. No hemos de olvidarnos, por último, que si usamos una función BeginGroup hemos de usar cuando acabemos de diseñar el grupo una función EndGroup obligatoriamente, para indicarle a Unity que las instrucciones referidas al grupo ya se han acabado.
EndGroup: static function EndGroup () : void
Finaliza un grupo.
BeginScrollView:
static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect) : Vector2 static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean) : Vector2 static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle) : Vector2 static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle) : Vector2
Inicia una vista de desplazamiento (scrollview) dentro de la GUI. Un scrollview nos permite poner una área más pequeña en la pantalla dentro de un área mucho mayor, usando barras de desplazamiento (scrollbars) a los lados. Esta función devuelve la posición del scroll modificada. Al igual que con otras variables, se recomienda reintroducir en la variable que se le pasa a la función los datos nuevos que ésta devuelve. Tiene estos parámetros:
position: scrollPosition:
Rectángulo en la pantalla a usar para el ScrollView. La distancia en píxeles que la vista es desplazada en las
direcciones X e Y. viewRect: El rectángulo usado dentro del scrollview. alwayShowHorizontal: Parámetro opcional para enseñar siempre el scrollbar horizontal. Si lo establecemos en falso o no lo aportamos a la función, sólo se enseñará cuando clientRect sea más ancho que la posición. alwayShowVertical: Lo mismo para el scrollbar vertical. horizontalScrollbar: GUIStyle opcional a usar por el scrollbar horizontal. Si no se aporta, se usará el estilo horizontalScrollbar del GUISkin que se esté usando. verticalScrollbar: Lo mismo para el scrollbar vertical
EndScrollView: static function EndScrollView () : void
Acaba un scrollview iniciado con una llamada a BeginScrollView.
ScrollTo:
static function ScrollTo (position : Rect) : void Desplaza todos los scrollviews incluidos para tratar de hacer visible una determinada posición.
Window:
static function Window (id : int, clientRect : Rect, func : WindowFunction, text : String) : Rect static function Window (id : int, clientRect : Rect, func : WindowFunction, image : Texture) : Rect static function Window (id : int, clientRect : Rect, func : WindowFunction, content : GUIContent) : Rect static function Window (id : int, clientRect : Rect, func : WindowFunction, text : String, style : GUIStyle) : Rect static function Window (id : int, clientRect : Rect, func : WindowFunction, image : Texture, style : GUIStyle) : Rect static function Window (id : int, clientRect : Rect, func : WindowFunction, title : GUIContent, style : GUIStyle) : Rect
Crea una ventana emergente y devuelve el rectángulo en el que dicha ventana se ubica. Las ventanas flotan sobre los controles GUI normales, Windows float above normal GUI controls, y pueden ser opcionalmente arrastrados por los usuarios finales. A diferencia de otros controles, necesitamos pasarles una función separada para los controles GUI para colocarlos dentro de la ventana. Nota: Si usamos GUILayout (de próxima explicación) para colocar nuestros componentes dentro de la ventana, debemos usar GUILayout.Window. Parámetros:
id: clientRect: func: debe
Una ID única a usar para cada ventana. Rectángulo en la pantalla a usar por el grupo. La función que crea el GUI dentro de la ventana. Esta función tomar un parámetro, que será la ID de la ventana para la que se
está text: image: content: style: usará el
creando la GUI. Texto a mostrar como título para la ventana. Textura que muestra una imagen en la barra del título. Texto, imagen y tooltip para esta ventana. Un estilo opcional a usar por la ventana. Si no se aporta, se estilo de ventana del GUISkin corriente.
Y vamos con un ejemplo:
var windowRect : Rect = Rect (20, 20, 120, 50); function OnGUI () {
windowRect = GUI.Window (0, windowRect, CreaMiVentana, "Mi ventana"); } function CreaMiVentana (windowID : int) { if (GUI.Button (Rect (10,20,100,20), "Hola mundo")) print ("Recibí un click"); }
Creamos una ventana con ID 0, que ubicamos en un rectángulo cuyas coordenadas y dimensiones están almacenadas en una variable, variable en la cual almacenaremos el rectángulo retornado por la función. Como tercer parámetro le pasamos una función que es la que crea los controles que irán dentro de la ventana, y por último el título de dicha ventana. La función que crea los controles toma como parámetro a su vez el primer parámetro de GUI.Window, que es la id de la ventana, y en este caso meramente creamos un botón con un texto, que al ser pulsado imprime un mensaje en plantalla.
91. CLASE GUI (y VIII)
DragWindow: static function DragWindow (position : Rect) : void Crea una ventana que puede arrastrarse. Si llamamos a esta función dentro del código de la ventana, automáticamente ésta podrá arrastrarse. Le hemos de pasar a la función un parámetro que indica la parte de la ventana que puede ser arrastrada, dando un rectángulo que recorta la ventana original. Para constatar lo que estoy diciendo, sólo tenéis que añadir esta línea a la función CreaMiVentana del ejemplo anterior:
GUI.DragWindow (Rect (0,0, 100, 20)); Pensad que 0,0 viene referido a la ventana emergente, no a las coordenadas generales. static function DragWindow () : void Esta función tiene un segundo prototipo que no requiere parámetros. Si queremos que nuestra ventana pueda ser arrastrada desde cualquier parte del background de la misma, es preferible utilizar esta forma de la función y colocarla al final de las funciones de la ventana. Así, si modificamos esta parte del script:
function CreaMiVentana (windowID : int) { if (GUI.Button (Rect (10,20,100,20), "Hola mundo")) print ("Recibí un click"); GUI.DragWindow (); } podremos arrastrar nuestra ventana emergente desde cualquier punto de ésta. BringWindowToFront: static function BringWindowToFront (windowID : int) : void
Trae una ventana determinada al frente del resto de ventanas flotantes. Tiene como único parámetro la ID de la ventana que queremos poner en primer plano. BringWindowToBack: static function BringWindowToBack (windowID : int) : void Coloca una ventana determinada al fondo de las ventanas flotantes. FocusWindow: static function FocusWindow (windowID : int) : void Hace que una ventana se convierta en la ventana activa. Se le pasa como parámetro la ID de dicha ventana. UnFocusWindow: static function UnfocusWindow () : void Quita el foco de todas las ventanas.
92. CLASE GUILAYOUT (I)
Esta clase es la interfaz para la gui de Unity con distribución automática. Me explico: Hay dos maneras que podemos usar para organizar y distribuir nuestras interfaces gráficas de usuario: fija y automática. Hasta ahora hemos trabajado con la clase GUI, que es la forma fija de distribución. Esto entraña que cada vez que se crea un nuevo elemento o control, se le ubica en un punto concreto (casi siempre a través de un Rect). En cambio, con la forma automática de distribución (que es la que permite la clase GUILayout) esto no es necesario. Se pueden usar ambos modos en la misma función OnGUI(). El modo fijo de distribución se suele usar cuando tenemos una interfaz prediseñada con la que trabajamos. El modo automático en cambio se suele usar cuando no sabemos cuántos elementos acabaremos necesitando, o no queremos preocuparnos de colocar a mano cada control. Hay dos diferencias a tener en cuenta cuando usamos distribución automática: 1.- Hemos de usar GUILayout en lugar de GUI. 2.- Para la distribución automática no se usa la función Rect().
FUNCIONES DE CLASE: Label: static function Label (image : Texture, params options : GUILayoutOption[]) : void static function Label (text : String, params options : GUILayoutOption[]) : void
static function Label (content : GUIContent, params options : GUILayoutOption[]) : void static function Label (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function Label (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function Label (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void
Hace una etiqueta de distribución automático. Las etiquetas no proveen interación con el usuario, no capturan clicks de ratón y son siempre renderizadas en estilo normal. Si quieres tener un control que responda visualmente a las entradas de usuario usa un box control. Todos los parámetros menos uno son idénticos a los de la función homónima de la clase GUI, así que a ellos me remito. Pero quiero detenerme en ese parámetro distinto, que de hecho es el que marca la diferencia entre la distribución fija (mediante Rect() y la automática. Me estoy refiriendo al parámetro params option, que es de tipo GUILayoutOption. GUILayoutOption es una clase internamente usada por Unity para pasar diferentes opciones de distribución en las funciones de la clase GUILayout. No se usa directamente, sino a través de funciones de tipo GUILayout, como por ejemplo: GUILayout.Width, GUILayout.Height, GUILayout.MinWidth, GUILayout.MaxWidth, GUILayout.MinHeight, GUILayout.MaxHeight, GUILayout.ExpandWidth y GUILayout.ExpandHeight. Más adelante en esta clase estudiaremos dichas funciones.
Box: static function Box (image : Texture, params options : GUILayoutOption[]) : void static function Box (text : String, params options : GUILayoutOption[]) : void static function Box (content : GUIContent, params options : GUILayoutOption[]) : void static function Box (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function Box (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function Box (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void crea una caja de distribución automática. Si queremos crear una caja con algún contenido dentro, hemos de usar el parámetro de estilo de uno de los subgrupos de funciones (BeginHorizontal, BeginVertical, etc...).
Button: static function Button (image : Texture, params options : GUILayoutOption[]) : boolean static function Button (text : String, params options : GUILayoutOption[]) : boolean static function Button (content : GUIContent, params options : GUILayoutOption[]) : boolean static function Button (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function Button (text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function Button (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : boolean
Crea un botón con distribución automática qevuelve true cuando el usuario lo presiona.
RepeatButton: static function RepeatButton (image : Texture, params options : GUILayoutOption[]) : boolean static function RepeatButton (text : String, params options : GUILayoutOption[]) : boolean static function RepeatButton (content : GUIContent, params options : GUILayoutOption[]) : boolean static function RepeatButton (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function RepeatButton (text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function RepeatButton (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : boolean
Crea un botón que devuelve true tanto tiempo como el usuario lo mantiene pulsado.
TextField:
static function TextField (text : String, params options : GUILayoutOption[]) : String static function TextField (text : String, maxLength : int, params options : GUILayoutOption[]) : String static function TextField (text : String, style : GUIStyle, params options : GUILayoutOption[]) : String static function TextField (text : String, maxLength : int, style : GUIStyle, params options : GUILayoutOption[]) : String
Crea un campo de texto de una linea donde el usuario puede editar un string.
PasswordField:
static function PasswordField (password : String, maskChar : char, params options : GUILayoutOption[]) : String static function PasswordField (password : String, maskChar : char, maxLength : int, params options : GUILayoutOption[]) : String static function PasswordField (password : String, maskChar : char, style : GUIStyle, params options : GUILayoutOption[]) : String static function PasswordField (password : String, maskChar : char, maxLength : int, style : GUIStyle, params options : GUILayoutOption[]) : String Crea un campo de texto donde el usuario puede entrar una contraseña. Devuelve la contraseña editada.
TextArea: static function TextArea (text : String, params options : GUILayoutOption[]) : String static function TextArea (text : String, maxLength : int, params options : GUILayoutOption[]) : String static function TextArea (text : String, style : GUIStyle, params options : GUILayoutOption[]) : String static function TextArea (text : String, maxLength : int, style : GUIStyle, params options : GUILayoutOption[]) : String
Crea un campo de texto multilínea donde el user puede editar un string, y devuelve dicho string
93. CLASE GUILAYOUT (II)
Toggle: static function Toggle (value : boolean, image : Texture, params options : GUILayoutOption[]) : boolean static function Toggle (value : boolean, text : String, params options : GUILayoutOption[]) : boolean static function Toggle (value : boolean, content : GUIContent, params options : GUILayoutOption[]) : boolean static function Toggle (value : boolean, image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function Toggle (value : boolean, text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean static function Toggle (value : boolean, content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : boolean Crea un botón que alterna on/off. Devuelve un booleano que indica el estado de dicho botón
Toolbar: static function Toolbar (selected : int, images : Texture[], params options : GUILayoutOption[]) : int static function Toolbar (selected : int, content : GUIContent[], params options : GUILayoutOption[]) : int static function Toolbar (selected : int, texts : string[], style : GUIStyle, params options : GUILayoutOption[]) : int static function Toolbar (selected : int, images : Texture[], style : GUIStyle, params options : GUILayoutOption[]) : int static function Toolbar (selected : int, contents : GUIContent[], style : GUIStyle, params options : GUILayoutOption[]) : int Crea un toolbar (barra de herramientas). Devuelve un int que contiene el índice del botón seleccionado.
SelectionGrid: static function SelectionGrid (selected : int, texts : string[], xCount : int, params options : GUILayoutOption[]) : int static function SelectionGrid (selected : int, images : Texture[], xCount : int, params options : GUILayoutOption[]) : int static function SelectionGrid (selected : int, content : GUIContent[], xCount : int, params options : GUILayoutOption[]) : int static function SelectionGrid (selected : int, texts : string[], xCount : int, style : GUIStyle, params options : GUILayoutOption[]) : int static function SelectionGrid (selected : int, images : Texture[], xCount : int, style : GUIStyle, params options : GUILayoutOption[]) : int static function SelectionGrid (selected : int, contents : GUIContent[], xCount : int, style : GUIStyle, params options : GUILayoutOption[]) : int Crea una cuadrícula de selección, devolviendo el int con el índice del botón seleccionado.
HorizontalSlider: static function HorizontalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float static function HorizontalSlider (value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb : GUIStyle, params options : GUILayoutOption[]) : float Crea una barra de desplazamiento horizontal que el usuario puede arrastrar desde un mínimo hasta un máximo. Devuelve un float con el valor que ha sido escogido por el usuario.
VerticalSlider: static function VerticalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float static function VerticalSlider (value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb : GUIStyle, params options : GUILayoutOption[]) : float Crea una barra de desplazamiento vertical que el usuario puede arrastrar desde un mínimo hasta un máximo.
HorizontalScrollbar: static function HorizontalScrollbar (value : float, size : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float static function HorizontalScrollbar (value : float, size : float, leftValue : float, rightValue : float, style : GUIStyle, params options : GUILayoutOption[]) : float Crea un scrollbar horizontal. Devuelve el valor modificado en float, que puede ser cambiado por el usuario arrastrando el scrollbar o haciendo click en las flechas al final.
VerticalScrollbar:
static function VerticalScrollbar (value : float, size : float, topValue : float, bottomValue : float, params options : GUILayoutOption[]) : float static function VerticalScrollbar (value : float, size : float, topValue : float, bottomValue : float, style : GUIStyle, params options : GUILayoutOption[]) : float Crea un scrollbar vertical.
94. CLASE GUILAYOUT (III)
Space: static function Space (pixels : float) : void
Inserta un espacio en el actual grupo de distribución. La dirección de dicho espacio dependerá del la dirección del grupo de distribución en el que estemos trabajando. Si por ejemplo se trata de un grupo vertical, el espacio será vertical. Observemos la diferencia con dos ejemplos:
function OnGUI () { GUILayout.Button ("Primer botón"); GUILayout.Space (20); GUILayout.Button ("Segundo botón"); }
Si ejecutamos este primer script, observaremos dos botones situados uno encima del otro, con un espacio (vertical) entre ambos de 20 píxeles. En cambio, si estuviéramos trabajando con un grupo de distribución horizontal...
function OnGUI () { GUILayout.BeginHorizontal(); GUILayout.Button ("Primer botón"); GUILayout.Space (20); GUILayout.Button ("Segundo botón");
GUILayout.EndHorizontal(); }
... nos encontraríamos con un botón al lado del otro separados por un espacio (horizontal) de 20 píxeles. (En breve estudiaremos las funciones BeginHorizontal y EndHorizontal, aunque supongo que se intuye su cometido)
FlexibleSpace: static function FlexibleSpace () : void
Inserta un elemento de espacio flexible. Esta función utiliza cualquier espacio que sobra en un diseño.
BeginHorizontal: static function BeginHorizontal (params options : GUILayoutOption[]) : void static function BeginHorizontal (style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void Empieza un grupo de controles horizontal. Todos los controles dentro de este elemento serán colocados horizontalmente uno junto a otro. El grupo debe cerrarse con una llamada a EndHorizontal. EndHorizontal: static function EndHorizontal () : void
Cierra el grupo abierto por BeginHorizontal.
BeginVertical: static function BeginVertical (params options : GUILayoutOption[]) : void static function BeginVertical (style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void Empieza un grupo de controles vertical. Todos los controles dentro de este elemento serán situados verticalmente uno debajo del otro. El grupo debe cerrarse con EndVertical
EndVertical: static function EndVertical () : void
Cierra un grupo iniciado por BeginVertical.
BeginArea:
static function BeginArea (screenRect : Rect) : void static function BeginArea (screenRect : Rect, text : String) : void static function BeginArea (screenRect : Rect, image : Texture) : void
static function BeginArea (screenRect : Rect, content : GUIContent) : void static function BeginArea (screenRect : Rect, style : GUIStyle) : void static function BeginArea (screenRect : Rect, text : String, style : GUIStyle) : void static function BeginArea (screenRect : Rect, image : Texture, style : GUIStyle) : void static function BeginArea (screenRect : Rect, content : GUIContent, style : GUIStyle) : void
Comienza un bloque GUILayout de controles GUI en un determinado área de la pantalla. Por defecto, cualquier control GUI hecho usando GUILayout es situado en la parte superior izquierda de la pantalla. Si queremos colocar una serie de controles en una zona arbitraria, hemos de usar esta función para definir un nuevo area. por ejemplo:
function OnGUI () { GUILayout.BeginArea (Rect (10,10,100,100)); GUILayout.Button ("Un botón"); GUILayout.Button ("Otro botón"); GUILayout.EndArea (); }
Aquí iniciamos un área de controles ubicada en las coordenadas 10,10 y con unas dimensiones de 100 X 100.
EndArea: static function EndArea () : void
Cierra un área de controles abierto con BeginArea.
BeginScrollView: static function BeginScrollView (scrollPosition : Vector2, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, style : GUIStyle) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, background : GUIStyle, params options : GUILayoutOption[]) : Vector2 Comienza un scrollview (o vista de desplazamiento) automáticamente distribuído. Este puede tomar cualquier contenido que le metas dentro y lo muestra normalmente (sin que se vea el scroll). Si no cabe el contenido, aparece la barra. Una llamada a esta función debe siempre acabar con una llamada a EndScrollView. Esta función devuelve un Vector2 con la posición del scroll modificada. Cuenta con los siguientes parámetros:
scrollPosition: La posición de uso de la pantalla. alwayShowHorizontal: Parámetro opcional para enseñar siempre el scrollbar horizontal. Si está en falso o no se aporta, sólo se mostrará el scrollbar cuando el contenido dentro del scrollview
sea más ancho que éste. alwayShowVertical: Lo mismo que el anterior para el scrollbar vertical. horizontalScrollbar: GUIStyle opcional a usar para el scrollbar horizontal. Si no se indica, se usará el estilo de horizontalScrollbar del GUISkin que se esté usando. verticalScrollbar Lo mismo pra el scrollbar vertical.
EndScrollView: static function EndScrollView () : void
Finaliza un scroll view empezado con una llamada a BeginScrollView.
94. CLASE GUILAYOUT (III)
Space: static function Space (pixels : float) : void
Inserta un espacio en el actual grupo de distribución. La dirección de dicho espacio dependerá del la dirección del grupo de distribución en el que estemos trabajando. Si por ejemplo se trata de un grupo vertical, el espacio será vertical. Observemos la diferencia con dos ejemplos:
function OnGUI () { GUILayout.Button ("Primer botón"); GUILayout.Space (20); GUILayout.Button ("Segundo botón"); }
Si ejecutamos este primer script, observaremos dos botones situados uno encima del otro, con un espacio (vertical) entre ambos de 20 píxeles. En cambio, si estuviéramos trabajando con un grupo de distribución horizontal...
function OnGUI () { GUILayout.BeginHorizontal(); GUILayout.Button ("Primer botón"); GUILayout.Space (20); GUILayout.Button ("Segundo botón"); GUILayout.EndHorizontal(); }
... nos encontraríamos con un botón al lado del otro separados por un espacio (horizontal) de 20 píxeles. (En breve estudiaremos las funciones BeginHorizontal y EndHorizontal, aunque supongo que se intuye su
cometido)
FlexibleSpace: static function FlexibleSpace () : void
Inserta un elemento de espacio flexible. Esta función utiliza cualquier espacio que sobra en un diseño.
BeginHorizontal: static function BeginHorizontal (params options : GUILayoutOption[]) : void static function BeginHorizontal (style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginHorizontal (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void Empieza un grupo de controles horizontal. Todos los controles dentro de este elemento serán colocados horizontalmente uno junto a otro. El grupo debe cerrarse con una llamada a EndHorizontal. EndHorizontal: static function EndHorizontal () : void
Cierra el grupo abierto por BeginHorizontal.
BeginVertical: static function BeginVertical (params options : GUILayoutOption[]) : void static function BeginVertical (style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void static function BeginVertical (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void Empieza un grupo de controles vertical. Todos los controles dentro de este elemento serán situados verticalmente uno debajo del otro. El grupo debe cerrarse con EndVertical
EndVertical: static function EndVertical () : void
Cierra un grupo iniciado por BeginVertical.
BeginArea:
static function BeginArea (screenRect : Rect) : void static function BeginArea (screenRect : Rect, text : String) : void static function BeginArea (screenRect : Rect, image : Texture) : void static function BeginArea (screenRect : Rect, content : GUIContent) : void static function BeginArea (screenRect : Rect, style : GUIStyle) : void static function BeginArea (screenRect : Rect, text : String, style : GUIStyle) : void static function BeginArea (screenRect : Rect, image : Texture, style : GUIStyle) : void static function BeginArea (screenRect : Rect, content : GUIContent, style : GUIStyle) : void
Comienza un bloque GUILayout de controles GUI en un determinado área de la pantalla. Por defecto, cualquier control GUI hecho usando GUILayout es situado en la parte superior izquierda de la pantalla. Si queremos colocar una serie de controles en una zona arbitraria, hemos de usar esta función para definir un nuevo area. por ejemplo:
function OnGUI () { GUILayout.BeginArea (Rect (10,10,100,100)); GUILayout.Button ("Un botón"); GUILayout.Button ("Otro botón"); GUILayout.EndArea (); }
Aquí iniciamos un área de controles ubicada en las coordenadas 10,10 y con unas dimensiones de 100 X 100.
EndArea: static function EndArea () : void
Cierra un área de controles abierto con BeginArea.
BeginScrollView: static function BeginScrollView (scrollPosition : Vector2, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, style : GUIStyle) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, params options : GUILayoutOption[]) : Vector2 static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, background : GUIStyle, params options : GUILayoutOption[]) : Vector2 Comienza un scrollview (o vista de desplazamiento) automáticamente distribuído. Este puede tomar cualquier contenido que le metas dentro y lo muestra normalmente (sin que se vea el scroll). Si no cabe el contenido, aparece la barra. Una llamada a esta función debe siempre acabar con una llamada a EndScrollView. Esta función devuelve un Vector2 con la posición del scroll modificada. Cuenta con los siguientes parámetros:
scrollPosition: La posición de uso de la pantalla. alwayShowHorizontal: Parámetro opcional para enseñar siempre el scrollbar horizontal. Si está en falso o no se aporta, sólo se mostrará el scrollbar cuando el contenido dentro del scrollview sea más ancho que éste. alwayShowVertical: Lo mismo que el anterior para el scrollbar vertical. horizontalScrollbar: GUIStyle opcional a usar para el scrollbar horizontal. Si no se indica, se usará el estilo de horizontalScrollbar del
GUISkin verticalScrollbar
que se esté usando. Lo mismo pra el scrollbar vertical.
EndScrollView: static function EndScrollView () : void
Finaliza un scroll view empezado con una llamada a BeginScrollView.
96. CLASE TEXTURE
Clase base para el manejo de textures. Contiene funcionalidades que son comuntes tanto en la clase Texture2D como en la clase RenderTexture. VARIABLES: width: Variable de sólo lectura que indica la anchura de la textura en píxeles.
height: var height : int
Variable de sólo lectura que indica la alturade la textura en píxeles.
filterMode: var filterMode : FilterMode
Modo de filtrado de la textura. La variable FilterMode es una enumeración que permite estos valores:
Point: bloques Bilinear: Trilinear: también
Filtrado de puntos, los píxeles de la textura se convierten en de cerca. Filtrado bilinear, las muestras de la textura se promedian. Filtrado trilinear, los píxeles de la textura se promedian y son mezclados entre los niveles del mipmap.
anisoLevel: var anisoLevel : int
Nivel de filtro anisotrópico de la textura (anisotropía recordemos que implica que algunas características del objeto, como la luz, dependen del lugar desde donde éste es observado.) El filtrado anisotrópico hace que las texturas luzcan mejor cuando son vistas en un ángulo bajo, pero a cambio de un importante coste de rendimiento en la tarjeta gráfica. Usualmente se usa esto en texturas de suelo, tierra o carreteras para que se vean mejor. El rango de valor de esta variable va de 1 a 9, donde 1 equivale no filtro aplicado y 9 implica filtro totalmente aplicado. A medida que el valor es más grande, la textura es más clara en los ángulos bajos.
wrapMode: var wrapMode : TextureWrapMode
Modo de envoltura de la textura. Esta puede establecerse en clamp (que podemos traducir por fija o pinzada) o en repear, que "azuleja" la textura en un número de repeticiones.
renderer.material.mainTexture.wrapMode = TextureWrapMode.Clamp;
TextureWrapMode como decimos es una enum con estos valores:
Repeat: Clamp:
Azuleja la textura, creando un patrón de repetición. Sujeta la textura al último pixel en el borde de ésta.
97. CLASE TEXTURE2D (I)
Es esta una clase para manejo de texturas. Se suele usar para crear texturas "al vuelo" (en tiempo de ejecución) o para modificar los assets de texturas existentes.
VARIABLES: mipmapCount: var mipmapCount : int Variable de sólo lectura que indica cuántos niveles de mipmap hay en la textura. El valor retornado incluye el nivel base también, razón por la cual esta variable siempre vale 1 o más. El conteo de mipmaps se usa en caso de que utilicemos las funciones GetPixels o SetPixels para obtener o modificar respectibamente los diferentes niveles de mipmal. Por ejemplo, podemos querer cambiar una textura de tal manera que cada nivel de mipmap se tinte de un color diferente, y así en el juego veríamos cuantos niveles de mipmap son en realidad visibles. textureFormat: var format : TextureFormat El formato de los datos de los píxeles en la textura (sólo lectura). FUNCIONES:
Texture2D: static function Texture2D (width : int, height : int) : Texture2D Crea una nueva textura vacía. Esta tendrá un tamaño dado por anchura y altura, con un formato de textura ARGV32 y con mipmaps. Normalmente querremos poner los colores de la textura después de crearla, usando las funciones SetPixel, SetPixels y Apply.
function Start () { var nuevaTextura = new Texture2D (128, 128); renderer.material.mainTexture = nuevaTextura; } Este sería el ejemplo básico. Creamos una nueva textura de dimensiones 128 X 128 y se la asignamos como principal textura al gameobject al que le vinculemos el script. En este caso eliminamos MiPrimerScript del gameobject PortaScripts y se lo vinculamos a la Esfera. Observaremos, si la tenemos seleccionada al darle al play, que ésta importa a vuelo la nueva textura, que por defecto es de color negro. static function Texture2D (width : int, height : int, format : TextureFormat, mipmap : boolean) : Texture2D Este segundo prototipo se diferencia del primero en que se le pasa a la función como parámetro un determinado formato para la textura y a la vez se indica si se crea con o sin mipmaps. SetPixel: function SetPixel (x : int, y : int, color : Color) : void Función que nos permite indicar el color de los píxeles en las coordenadas que le pasamos. No obstante, para actualizar en realidad el color de dichos píxeles en la tarjeta gráfica del usuario, deberemos acto seguido llamar a la función Apply. Dado que esta manera de actualizar las texturas consume bastantes recursos, así que sería deseable que cambiáramos tantos píxeles como fuera posible entre cada llamada a la función Apply. Si tenemos pensado regenerar constantemente texturas en tiempo de ejecución, puede ser más rápido generar un array de colores de píxeles y asignarlos todos a la vez con la función SetPixels. Esta función, por último, trabaja sólo con los formatos ARGB32, RGB24 y Alpha8. Para el resto de formatos SetPixel es ignorada. Vamos a verlo con un ejemplo. Previamente sería interesante colocar la cámara en las siguientes coordenadas: position(0,1.5,-6), rotation(10,0,0). Así tendremos una mejor visión de lo que le acontecerá a la esfera en el próximo script. Editamos MiPrimerScript ahora:
function Start () { var nuevaTextura = new Texture2D(128, 128); renderer.material.mainTexture = nuevaTextura; var mitadH : int = nuevaTextura.width/2; var mitadV : int = nuevaTextura.height/2; var unColor : Color = Color.green; for (var x : int = 0; x < mitadH; x++){ for (var y : int = 0; y < mitadV; y++){ nuevaTextura.SetPixel(x, y, unColor); } } nuevaTextura.Apply(); } Vamos por partes. Creamos primero una nueva textura y se la pasamos al gameobject al que tenemos
vinculado el script. Almacenamos acto seguido en sendos integers la mitad de la altura y la anchura de la nueva textura. De esta manera, en un bucle for podemos asignar un determinado color a esa mitad. Recordemos en todo caso dejar la función Apply fuera del bucle, a los efectos de no sobrecargar el pc. GetPixel: function GetPixel (x : int, y : int) : Color Devuelve el color del píxel que se halla en las coordenadas dadas. Si éstas están fuera de los bordes (más grandes de la anchura/altura o más pequeñas que cero) serán fijadas o repetidas en base al wrapmode de la textura Si quieres leer un bloque amplio de píxeles de una textura, será más rápido usar GetPixels, la cual devuelve un bloque entero de colores de píxeles. La textura debe tener activa la variable Is Readable en import settings, ya que de lo contrario fallará la función.
98. CLASE TEXTURE2D (y II)
GetPixelBilinear: function GetPixelBilinear (u : float, v : float) : Color
Devuelve el color de los píxeles filtrado en coordenadas normalizadas (u, v). dichas coordenadas normalizadas u y v van de 0.0 a 1.0, al igual que las coordenadas UV en mallas.
SetPixels: function SetPixels (colors : Color[], miplevel : int = 0) : void
Establece un bloque de colores de píxeles. Esta función toma un array de colores y cambia los colores de los píxeles del nivel mip entero de la textura. Precisa una posterior llamada a Aply para que los cambios se actualicen en la tarjeta gráfica. El array de colores es un array 2d plano, donde los píxeles se distribuyen de izquierda a derecha y de abajo arriba. El tamaño del array debe ser al menos la anchura por la altura o el level de mapeo mip usado. Usar SetPixels puede ser más rápido que llamar a SetPixel repetidamente, especialmente para texturas grandes. Además, SetPixels puede acceder a niveles de mipmap individuales. function SetPixels (x : int, y : int, blockWidth : int, blockHeight : int, colors : Color[], miplevel : int = 0) : void Esta versión de la función no modifica el nivel mip entero, sino sólo la parte comprendida entre los parámetros blockWidth y blockHeight empezando desde x,y. El array de colores debe tener un tamaño de blockWidth*blockHeight, y el bloque modificado debe caber dentro del nivel mip usado.
SetPixels32: function SetPixels32 (colors : Color32[], miplevel : int = 0) : void
Establece un bloque de colores de píxeles. Esta función toma un array de tipo Color32 y cambia los colores de los píxeles del nivel de mip entero de la textura. No nos olvidemos de llamar después a Apply.
LoadImage: function LoadImage (data : byte[]) : boolean
Carga una imagen de tipo JPG o PNG desde un array de bytes.
GetPixels: function GetPixels (miplevel : int = 0) : Color[] Esta función devuelve un array de colores de píxeles del nivel de mip entero de la textura. function GetPixels (x : int, y : int, blockWidth : int, blockHeight : int, miplevel : int = 0) : Color[] Este segundo prototipo retorna sólo el mip de la región blockWidth por blockHeight empezando por x,y.
GetPixels32: function GetPixels32 (miplevel : int = 0) : Color32[]
Obtiene un bloque de colores de píxeles en formato Color32.
Apply: function Apply (updateMipmaps : boolean = true, makeNoLongerReadable : boolean = false) : void
Aplica en la práctica todos los cambios previamente efectuados con las funciones SetPixel y SetPixels. Si el parámetro updateMipmaps es true, los niveles de mipmap son recalculados también, usando el nivel base como fuente. Normalmente tendremos este parámetro en true salvo cuando queramos modificar los niveles mip nosotros mismos usando SetPixels. Si el parámetro makeNoLongerReadable es true, la textura se marcará como no legible y la memoria será liberada en la siguiente actualización. Por defecto makeNoLongerReadable se pone en false. Esta función, como ya dijimos previamente, consume muchos recurso, así que es deseable hacer el mayor número de cambios precisos entre cada llamada a la misma.
Resize: function Resize (width : int, height : int, format : TextureFormat, hasMipMap : boolean) : boolean
Redimensiona la textura, y en concreto modifica la anchura, altura, formato y en ocasiones crea mipmaps. Esta función es muy similar al constructor, salvo que esta trabaja con una textura que ya existe. precisa llamar a Apply para que los cambios tengan efecto en la tarjeta gráfica. function Resize (width : int, height : int) : boolean Esta variante cambia meramente la altura y o la anchura de la textura. ReadPixels:
function ReadPixels (source : Rect, destX : int, destY : int, recalculateMipMaps : boolean = true) : void Lee píxeles de pantalla dentro de los datos de la textura guardada. Copia un área de píxeles rectangular de la RenderTexture en uso o bien la vista (especificada por el parámetro source) dentro de la posición definida por destX y destY. Ambas coordenadas usan el espacio de píxeles, donde (0,0) is abajo a la izquierda. Si recalculateMipMaps está establecido en true, los mip maps de la textura serán tambien actualizados. En caso contrario deberemos llamar a Apply para recalcularlos. Esta función trabaja sólo con los formatos de textura ARGB32 y RGB24. EncodeToPNG: function EncodeToPNG () : byte[] Codifica esta textura en formato PNG. El array de bytes que devuelve es el archivo PNG. Puedes escribirlo entonces en disco o enviarlo a través de la rec, por ejemplo. Esta función solo trabaja con los formatos de textura ARGB32 y RGB24. La textura también debe tener el flag Is Readable marcado en import settings.
99. CLASE INPUT (I)
Es la interfaz que controla todo el sistema de entradas (inputs) de Unity. Esta clase se usa, por ejemplo, para leer la configuración de ejes en el input Manager. Para leer un eje usaríamos Input.GetAxis con uno de los ejes por defecto: “Horizontal” y “Vertical” están configurados para joystick, así como A,W,S,D, y las teclas de flecha. “MouseX” y “Mouse Y” están configurados para el movimiento del ratón. Por su parte “Fire1", "Fire2" y "Fire3" están configurados para Ctrl, Alt y tres botones de ratón o joystick. Pueden añadirse nuevos ejes de entrada en el Input Manager. Si hemos se usar inputs para cualquier tipo de comportamiento que entrañe movimiento, es aconsejable usar Input.GetAxis. Esto nos dará una entrada más suave y configurable que puede servir para teclado, joystick o mouse. En cambio es mejor usar Input.GetButton para acciones como eventos, mejor que para movimientos. Las llamadas a inputs se deben hacer dentro de la función update.
VARIABLES DE CLASE: mousePosition: static var mousePosition : Vector3
Variable de sólo lectura que indica la posición actual del ratón en coordenadas de píxeles, donde la esquina inferior izquierda de la pantalla o ventana está en (0, 0) y la superior derecha en (Screen.width, Screen.height). Podemos probarlo con un ejemplo sencillo, para el cual eliminamos el script vinculado a la esfera, y reeditamos MiPrimerScript como sigue:
function Update(){ var apunten : Vector3 = Input.mousePosition; Debug.Log(apunten); }
Salvamos y arrastramos hasta PortaScripts. Meramente se nos imprimirá en pantalla la posición exacta en píxeles de nuestro cursor. Podemos observar que la esquina inferior izquierda se mueve en parámetros del 0,0.
anyKey: static var anyKey : boolean
Booleano de sólo lectura que comprueba si hay alguna tecla o botón del ratón apretada en ese momento. Podemos modificar nuestro script anterior para ilustrar esta variable:
function Update(){ if(Input.anyKey){ Debug.Log("Se ha pulsado una tecla o botón"); } }
No tenemos más que apretar cualquier tecla o botón del ratón para ver el mensaje impreso.
anyKeyDown: static var anyKeyDown : boolean
Variable de sólo lectura que devuelve true el primer frame en que el usuario golpea cualquier tecla o botón del ratón. Debemos colocar esta variable dentro de la función update, ya que el estado de la misma se resetea cada frame. No devolverá true de nuevo hasta que el usuario libere todas las teclas/botones y presione alguna tecla/botón otra vez. Para entender gráficamente la diferencia entre la variable anterior y la siguiente vamos a hacer una cosa: volvemos a darle al play (sigue en vigor el ejemplo anterior) y hacemos un click con un botón del ratón. Automáticamente nos aparece el mensaje impreso bajo la ventana del juego. Hacemos un click ahora sobre dicho mensaje, para que nos aparezca el popup de la consola, tal como muestra la captura:
Vale. Ahora, sin retirar dicho popup, presionamos sin soltar el botón del mouse sobre la ventana Game. Vemos que se imprime de manera continuada el mensaje (una por cada frame que mantenemos pulsado el botón del ratón). Y ahora sustituimos en el script la variable "anyKey" por "anyKeyDown", y repetimos los pasos anteriorer. Comprobaremos que aunque mantengamos el botón del ratón presionado, el mensaje sólo se imprime el primer frame que lo pulsamos, y no se vuelve a imprimir hasta que lo soltamos y lo volvemos a apretar.
inputString: static var inputString : String
Variable de sólo lectura que devuelve la entrada de teclado introducida este frame. La misma sólo puede contener caracteres ASCII o un par de caracteres especiales que deben ser manejados: el carácter “\b” significa retroceso y “\n” representa return o enter.
acceleration: static var acceleration : Vector3
Ultima medicion de aceleración lineal de un dispositimo en espacio tridimensional. Sólo lectura
100. CLASE INPUT (II)
accelerationEvents: static var accelerationEvents : AccelerationEvent[] Variable de sólo lectura que devuelve una lista de mediciones de aceleración ocurridas durante el último frame. Dichas medidas son alojadas en variables temporales.
Dichas variables temporales son un array del tipo AccelerationEvent, que es una estructura con dos valores: acceleration: De tipo Vector3, es el valor de aceleración. deltaTime: De tipo float, el tiempo transcurrido desde la última medición de aceleración.
accelerationEventCount: static var accelerationEventCount : int
Número de mediciones de aceleración ocurrida durante el último frame.
eatKeyPressOnTextFieldFocus: static var eatKeyPressOnTextFieldFocus : boolean
Propiedad que indica si las teclas impresas son comidas por la entrada de texto si esta tiene el foco (por defecto true).
FUNCIONES DE CLASE: GetAxis: static function GetAxis (axisName : String) : float
Devuelve el valor del eje virtual identificado por axisName. El valor estará en el rango -1...1 para entradas de teclado (tradicionalmente la flechas de desplazamiento) y joystick. Si el axis es indicado por el movimiento del ratón, éste será multiplicado por el eje de sensibilidad y su rango será distinto a -1…1. Es una de las funciones con las que más tropezaremos cuando programemos con Unity, así que merece la pena que nos detengamos para ilustrarla con algún ejemplo. El primer ejemplo lo hemos sacado del manual de referencia. Para que funciones bien (recordemos que tenemos en la actualidad MiPrimerScript vinculado a la esfera) es necesario que eliminemos el componente Rigidbody de la esfera. Y acto seguido editamos nuestro script como sigue:
var velocidad : float = 2.0; var velocidadRotacion : float = 45.0; function Update () { var translacion : float = Input.GetAxis ("Vertical") * velocidad; var rotacion : float = Input.GetAxis ("Horizontal") * velocidadRotacion; translacion *= Time.deltaTime; rotacion *= Time.deltaTime; transform.Translate (0, 0, translacion); transform.Rotate (0, rotacion, 0); }
Tratemos de explicar lo que hemos hecho. A través de Input.GetAxis recogemos las entradas de teclado provinentes de las flechas de desplazamiento verticales y horizontales. Así, la flecha de desplazamiento hacia arriba vale 1 y la de desplazamiento hacia abajo vale -1, y lo mismo hacia la derecha (1) y la izquierda (-1). Dichos valores son multiplicados por la velocidad contenida en la variable "velocidad" en el
caso del eje arriba/abajo, y por la contenida en la variable "velocidadRotacion" para el eje derecha/izquierda. Ambos valores son almacenados en variables de tipo Vector3 que luego -tras ser reconvertidos en velocidad por frame a velocidad por segundo- se utilizan para participar en el movimiento del game object tanto en el eje delante/detrás de translación como en el eje Y de rotación. En suma, nos queda un script -muy básico y torpe, eso sí- para manejar el desplazamiento y giro de nuestra esfera en base a las flechas de desplazamiento. Probémoslo un rato. Hemos dicho que Input.GetAxis también acepta como entrada el provocado por el movimiento vertical y horizontal del ratón, así que ilustrémoslo:
var velocidadHorizontal : float = 20.0; var velocidadVertical : float = 20.0; function Update () { var h : float = velocidadHorizontal * Input.GetAxis ("Mouse X"); var v : float = velocidadVertical * Input.GetAxis ("Mouse Y"); transform.Rotate (v, h, 0); }
Es un ejemplo sencillo. Se almacena en sendas variables el fruto de multiplicar el valor de los ejes de desplazamiento horizontal y vertical del ratón (que recordemos que no es -1,1) por el valor de velocidad que le hayamos dado a las variables expuestas del inicio de script. En base a ello, nuestra esfera girará sobre su eje X e Y en respuesta a los movimientos vertical y horizontal de nuestro ratón.
GetAxisRaw: static function GetAxisRaw (axisName : String) : float
Devuelve el valor del eje virtual identificado por el parámetro axisName sin ningún filtro de suavizado aplicado. El valor estará en el rango de -1…1 para entrada de teclado y joystick. Como a la entrada -al contrario de la función anterior- no se le aplica smooth (suavizado),la entrada de teclado será siempre o -1 o cero o 1. Esta función puede sernos útil para el caso de que queramos hacer todo el proceso de suavizado de entrada nosotros mismos manualmente.
GetButton: static function GetButton (buttonName : String) : boolean
Devuelve true mientras el botón virtual que le pasemos como parámetro en formato string esté presionado. Podemos pensar por ejemplo en un disparador automático, que devolvería true mientras el botón estuviera presionado. Eso sí, así como esta función es óptima para acciones como disparar un arma, para cualquier tipo de movimiento es mejor usar GetAxis, que a éste le introduce valores de suavizado que en GetButton no hallaremos (ni podremos implementar manualmente, al contrario que en GetAxisRaw. Un ejemplo:
var proyectil : GameObject; var frecuenciaDisparo : float = 0.5; private var proximoDisparo : float = 0.0; function Update () { if (Input.GetButton ("Fire1") && Time.time > proximoDisparo) { proximoDisparo = Time.time + frecuenciaDisparo; var clon : GameObject = Instantiate(proyectil, transform.position+Vector3.forward,
transform.rotation) as GameObject; } }
Salvamos y arrastramos el cubo a la variable expuesta proyectil. Pulsamos play y observaremos que estamos clonando/disparando cubos cada vez que -dentro del lapso de tiempo permitido- pulsamos el botón izquierdo del ratón o la tecla Ctrl situada a la izquierda del teclado (que son por defecto los dos elementos que tenemos vinculados al evento Fire1. Por otro lado, si mantenemos pulsado de manera ininterrumpida bien el botón o bien la tecla indicados, observaremos que disparamos un cubo cada medio segundo. En sí el script comprueba si hemos pulsado la tecla o botón que tengamos asignada a Fire1. En caso afirmativo pasa a comprobar si ha transcurrido un lapso de tiempo superior al que le hemos fijado en frecuenciaDisparo (que para el primer disparo valdrá cero). Si también es true esta segunda condición se le añade medio segundo de espera al resto de disparos más el tiempo transcurrido en hacerlo, el script nos permite clonar/disparar otro proyectil más.
GetButtonDown: static function GetButtonDown (buttonName : String) : boolean
Devuelve true durante el frame en que el jugador aprieta el botón virtual identificado como buttonName. Debemos llamar siempre a esta función desde la función Update, dado que el estado se resetea cada frame. No devolverá true hasta que el usuario libere la tecla y la presione de nuevo, al igual que sucedía con anyKeyDown.
GetButtonUp: static function GetButtonUp (buttonName : String) : boolean
Devuelve true el primer frame en que el jugador libera el botón virtual identificado como buttonName. Recordemos llamar esta función desde Update ya que se resetea su estado cada frame. No devolverá true hasta que se libere la tecla y se vuelva a presionar.
101. CLASE INPUT (III)
GetKey: static function GetKey (name : String) : boolean
Devuelve true mientras el jugador aprieta la tecla identificada como name (pensemos de nuevo en un disparador automático) Para ver la lista de indentificadores de tecla podemos consultar Input Manager en el menú=>Edit=>Project Settings=>Input. Un ejemplo sencillo:
function Update () { if (Input.GetKey ("up")) print ("Has presionado la flecha de desplazamiento superior"); if (Input.GetKey ("down")) print ("Has presionado la flecha de desplazamiento inferior"); } static function GetKey (key : KeyCode) : boolean En este segundo prototipo la función devuelve true mientras el jugador presiona la tecla identificada por el parámetro de tipo KeyCode. Así, el ejemplo anterior en esta segunda modalidad se quedaría así:
function Update () { if (Input.GetKey (KeyCode.UpArrow)) print ("Has presionado la flecha de desplazamiento superior"); if (Input.GetKey (KeyCode.DownArrow)) print ("Has presionado la flecha de desplazamiento inferior"); } Paso a relacionar todo el enum KeyCode:
None Backspace Delete Tab Clear Return Pause Escape Space Keypad0 Keypad1 Keypad2 Keypad3 Keypad4 Keypad5 Keypad6 Keypad7
Not assigned (never is pressed) The backspace key The forward delete key The tab key The Clear key Return key Pause on PC machines Escape key Space key Numeric keypad 0 Numeric keypad 1 Numeric keypad 2 Numeric keypad 3 Numeric keypad 4 Numeric keypad 5 Numeric keypad 6 Numeric keypad 7
Keypad8 Keypad9 KeypadPeriod KeypadDivide KeypadMultiply KeypadMinus KeypadPlus KeypadEnter KeypadEquals UpArrow DownArrow RightArrow LeftArrow Insert Home End PageUp PageDown F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 Alpha0 Alpha1 Alpha2 Alpha3 Alpha4 Alpha5 Alpha6 Alpha7 Alpha8 Alpha9 Exclaim DoubleQuote Hash Dollar Ampersand Quote LeftParen RightParen Asterisk Plus Comma Minus Period Slash Colon Semicolon Less Equals Greater Question At LeftBracket Backslash RightBracket Caret
Numeric keypad 8 Numeric keypad 9 Numeric keypad '.' Numeric keypad '/' Numeric keypad '*' Numeric keypad '-' Numeric keypad '+' Numeric keypad enter Numeric keypad '=' Up arrow key Down arrow key Right arrow key Left arrow key Insert key key Home key End key Page up Page down F1 function key F2 function key F3 function key F4 function key F5 function key F6 function key F7 function key F8 function key F9 function key F10 function key F11 function key F12 function key F13 function key F14 function key F15 function key The '0' key on the top The '1' key on the top The '2' key on the top The '3' key on the top The '4' key on the top The '5' key on the top The '6' key on the top The '7' key on the top The '8' key on the top The '9' key on the top Exclaim key Double quote key Hash key Dollar sign key Ampersand key Quote key Left Parent key Right Parent key Asterisk key Plus key Comma ',' key Minus '-' key Period '.' key Slash '/' key Colon ',' key Semicolon ';' key Less '<' key Equals '=' key Greater '>' key Question mark '?' key At key Left bracket key Backslash key Backslash key Caret key
of of of of of of of of of of
the the the the the the the the the the
alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric alphanumeric
keyboard. keyboard. keyboard. keyboard. keyboard. keyboard. keyboard. keyboard. keyboard. keyboard.
Underscore BackQuote A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Numlock CapsLock ScrollLock RightShift LeftShift RightControl LeftControl RightAlt LeftAlt LeftApple LeftWindows RightApple RightWindows AltGr Help Print SysReq Break Menu Mouse0 Mouse1 Mouse2 Mouse3 Mouse4 Mouse5 Mouse6 JoystickButton0 JoystickButton1 JoystickButton2 JoystickButton3 JoystickButton4 JoystickButton5 JoystickButton6 JoystickButton7 JoystickButton8 JoystickButton9 JoystickButton10 JoystickButton11 JoystickButton12 JoystickButton13
Underscore '_' key Back quote key 'a' key 'b' key 'c' key 'd' key 'e' key 'f' key 'g' key 'h' key 'i' key 'j' key 'k' key 'l' key 'm' key 'n' key 'o' key 'p' key 'q' key 'r' key 's' key 't' key 'u' key 'v' key 'w' key 'x' key 'y' key 'z' key Numlock key Capslock key Scroll lock key Right shift key Left shift key Right Control key Left Control key Right Alt key Left Alt key Left Apple key Left Windows key Right Apple key Right Windows key Alt Gr key Help key Print key Sys Req key Break key Menu key First (primary) mouse button Second (secondary) mouse button Third mouse button Fourth mouse button Fifth mouse button Sixth mouse button Seventh mouse button Button 0 on any joystick Button 1 on any joystick Button 2 on any joystick Button 3 on any joystick Button 4 on any joystick Button 5 on any joystick Button 6 on any joystick Button 7 on any joystick Button 8 on any joystick Button 9 on any joystick Button 10 on any joystick Button 11 on any joystick Button 12 on any joystick Button 13 on any joystick
JoystickButton14 JoystickButton15 JoystickButton16 JoystickButton17 JoystickButton18 JoystickButton19 Joystick1Button0 Joystick1Button1 Joystick1Button2 Joystick1Button3 Joystick1Button4 Joystick1Button5 Joystick1Button6 Joystick1Button7 Joystick1Button8 Joystick1Button9 Joystick1Button10 Joystick1Button11 Joystick1Button12 Joystick1Button13 Joystick1Button14 Joystick1Button15 Joystick1Button16 Joystick1Button17 Joystick1Button18 Joystick1Button19 Joystick2Button0 Joystick2Button1 Joystick2Button2 Joystick2Button3 Joystick2Button4 Joystick2Button5 Joystick2Button6 Joystick2Button7 Joystick2Button8 Joystick2Button9 Joystick2Button10 Joystick2Button11 Joystick2Button12 Joystick2Button13 Joystick2Button14 Joystick2Button15 Joystick2Button16 Joystick2Button17 Joystick2Button18 Joystick2Button19 Joystick3Button0 Joystick3Button1 Joystick3Button2 Joystick3Button3 Joystick3Button4 Joystick3Button5 Joystick3Button6 Joystick3Button7 Joystick3Button8 Joystick3Button9 Joystick3Button10 Joystick3Button11 Joystick3Button12 Joystick3Button13 Joystick3Button14 Joystick3Button15 Joystick3Button16 Joystick3Button17 Joystick3Button18 Joystick3Button19
Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button Button
14 on any joystick 15 on any joystick 16 on any joystick 17 on any joystick 18 on any joystick 19 on any joystick 0 on first joystick 1 on first joystick 2 on first joystick 3 on first joystick 4 on first joystick 5 on first joystick 6 on first joystick 7 on first joystick 8 on first joystick 9 on first joystick 10 on first joystick 11 on first joystick 12 on first joystick 13 on first joystick 14 on first joystick 15 on first joystick 16 on first joystick 17 on first joystick 18 on first joystick 19 on first joystick 0 on second joystick 1 on second joystick 2 on second joystick 3 on second joystick 4 on second joystick 5 on second joystick 6 on second joystick 7 on second joystick 8 on second joystick 9 on second joystick 10 on second joystick 11 on second joystick 12 on second joystick 13 on second joystick 14 on second joystick 15 on second joystick 16 on second joystick 17 on second joystick 18 on second joystick 19 on second joystick 0 on third joystick 1 on third joystick 2 on third joystick 3 on third joystick 4 on third joystick 5 on third joystick 6 on third joystick 7 on third joystick 8 on third joystick 9 on third joystick 10 on third joystick 11 on third joystick 12 on third joystick 13 on third joystick 14 on third joystick 15 on third joystick 16 on third joystick 17 on third joystick 18 on third joystick 19 on third joystick
102. CLASE INPUT (y IV)
GetKeyDown: static function GetKeyDown (name : String) : boolean
Devuelve true durante el frame en que el usuario empieza a presionar la tecla identificada como name. Recordemos llamarla dentro de la función Updata, ya que resetea su estado cada frame. No devuelve true hasta que el usuario suelta y luego aprieta la tecla de nuevo (tal como hace por ejemplo GetButtonDown con respecto a GetButton).
static function GetKeyDown (key : KeyCode) : boolean
Devuelve true durante el frame en que el jugador empieza a presionar la tecla identificada por la key de tipo enumeración KeyCode, que vimos en el capítulo anterior.
GetKeyUp: static function GetKeyUp (name : String) : boolean static function GetKeyUp (key : KeyCode) : boolean Devuelve true durante el frame en que el jugador libera la tecla identificada por name.
GetJoystickNames: static function GetJoystickNames () : string[]
Devuelve un array de strings describiendo los joysticks conectados. Esto puede ser útil en una configuranción de entradas de pantalla de usuario. Así, en lugar de enseñar etiquetas como “joystick 1”, puedes mostrasr títulos más personalizados.
GetMouseButton: static function GetMouseButton (button : int) : boolean
Devuelve true si el botón indicado del ratón es apretado. La variable button es un int que representa 0 para el botón izquierdo, 1 para el derecho y 2 para el central. Por poner un ejemplo muy simple:
function Update() { if(Input.GetMouseButton(0)) Debug.Log("presionado botón izquierdo."); if(Input.GetMouseButton(1)) Debug.Log("presionado botón derecho."); if(Input.GetMouseButton(2)) Debug.Log("presionado botón central."); }
GetMouseButtonDown: static function GetMouseButtonDown (button : int) : boolean
Devuelve true durante el frame en que el usuario aprieta el botón del ratón indicado. Debes llamar esta función dentro de update, ya que el estado se resetea cada frame. No devolverá true hasta que el botón sea liberado y vuelto a pulsar (recordemos de nuevo la diferencia de GetButtonDown con respecto a GetButton, para aplicarla también aquí).
GetMouseButtonUp: static function GetMouseButtonUp (button : int) : boolean
Devuelve true durante el frame en que el usuario libera el botón del ratón indicado.
ResetInputAxes: static function ResetInputAxes () : void
Resetea todos los inputs, con lo que todos los axes y botones retornan a 0. Esto puede ser util cuando se regenera al jugador y no te interesa conservar ningún imput que proceda de alguna tecla que pudiera continuar presionando.
GetAccelerationEvent: static function GetAccelerationEvent (index : int) : AccelerationEvent
Devuelve mediciones de aceleración que ocurrieron durante el último frame.
103. ESTRUCTURA BOUNDS
Como es fácil apreciar, Bounds no es una clase, sino una estructura. La diferencia, aunque a efectos prácticos una clase es lo mismo que una estructura, es más de tipo semántico. Esto es, cuando nos
topamos con una estructura, se nos quiere informar de que la misma tiene un componente de complemento a una o varias clases que no suelen tener las clases en sí. La estructura bounds representa una caja de bordes con los ejes alineados. También denominada AABB (para abreviar axis-aligned bounding box), es una caja alineada con los ejes de coordenadas globales que envuelve algún objeto. Como la caja nunca rota respecto de los ejes, puede ser definida por su centro y extensiones, o alternativamente por mínimo y máximo. (center, extents, min y max, en inglés) La estructura Bounds se usa en Collider.bounds, Mesh.bounds y Renderer.bounds.
VARIABLES: center: var center : Vector3 El centro de la caja.
size: var size : Vector3
El tamaño total de la caja. Es siempre dos veces más grande que las extensiones (extents)
extents: var extents : Vector3
Las extensiones de la caja. Es siempre la mitad del tamaño (size)
min: var min : Vector3
El punto mínimo de la caja. Es siempre igual a center – extents.
max: var max : Vector3
El punto máximo de la caja. Siempre es igual a center+extents.
FUNCIONES: static function Bounds (center : Vector3, size : Vector3) : Bounds
Crea una nueva caja de bordes con un centro dado y un tamaño total. Las extensiones deben ser la mitad del tamaño dado.
SetMinMax: function SetMinMax (min : Vector3, max : Vector3) : void
Establece los bordes en los valores mínimos y máximos de la caja. Usar esta función es más rápido que asignar min y max de manera separada.
Encapsulate: function Encapsulate (point : Vector3) : void function Encapsulate (bounds : Bounds) : void
Incrementa la caja para incluir el punto que se pasa como parámetro (1er prototipo) o para incluir la nueva caja (2º prototipo)
Expand: function Expand (amount : float) : void function Expand (amount : Vector3) : void
Expande la caja para incrementar su tamaño en una cantidad a lo largo de cada cara.
Intersects: function Intersects (bounds : Bounds) : boolean
¿Hay alguna otra caja intersectando nuestra caja?
Contains: function Contains (point : Vector3) : boolean
¿Está el punto point contenido en la caja?
sqrDistance: function SqrDistance (point : Vector3) : float El cuadrado de la distancia entre el punto point y la caja.
intersectRay: function IntersectRay (ray : Ray) : boolean
¿El rayo ray intersecta esta caja? Pongamos un ejemplo. Previamente ubicamos la esfera (que es el gameobject al que tenemos vinculado nuestro script) en la posicion (2,0,2), y le añadimos de nuevo un componente Rigidbody. Ahora reeditamos MiPrimerScript así:
var rayo : Ray = new Ray (Vector3.zero, Vector3.forward);; function Update () { Debug.DrawRay (Vector3.zero, Vector3.forward * 999, Color.green); var bordes : Bounds = transform.collider.bounds; if (bordes.IntersectRay (rayo)) Debug.Log("La caja tocó el rayo");
} function FixedUpdate() { rigidbody.AddForce(-Vector3.right*6*Time.deltaTime); }
Creamos primero un rayo que surge en el centro global de la escena y se prolonga a lo largo del eje z positivo (vulgarmente, hacia delante). Ya dentro de la función update dibujamos ese mismo rayo que ya tenemos descrito, para que visualmente podamos seguir lo que está pasando. Acto seguido creamos una instancia de la estructura Bounds que en un alarde de originalidad llamaremos bordes, y la inicializaremos con la caja de bordes del collider de nuestro gameobject (la caja que envuelve el collider, ojo, no confundir con el collider mismo. En este caso el collider tiene forma de escena y el Bounds de la esfera sigue teniendo forma de caja). Dejamos establecido que si la caja toca el rayo se mostrará un mensaje en pantalla, así que sólo nos queda darle movimiento a la esfera para que cuando intersecte el rayo (que aquí hemos dibujado a través de DrawRay, pero que podríamos haber dejado invisible) se muestre dicho mensaje.
104. CLASE EVENT (II)
clickCount: var clickCount : int
Cuántos clicks de ratón consecutivos hemos recibido. Es usado en el evento EventType.MouseDown. Usadlo para diferenciar entre un click único y un doble click. Un ejemplo:
private var numeroClicks : int = 0; function OnGUI() { var miEvento : Event = Event.current; if (miEvento.isMouse) { numeroClicks +=miEvento.clickCount; Debug.Log("Mouse clicks: " + numeroClicks); } }
Si lo probamos, vemos que tenemos un contador de clicks que contabiliza cada actividad (down y up) del botón del ratón desde el inicio del juego. Es una adaptación del script que está en el manual de referencia. Si os fijáis, declaramos la variable numeroClicks fuera de la función onGUI, para que no nos contabilice (como hace en el manual de referencia) los clicks de cada frame, sino los totales. Por lo demás, el script no tiene mucho misterio: inicializamos una variable de tipo Event con el evento actual, nos aseguramos de que el evento tenga que ver con el ratón y pasamos a contar clicks.
character: var character : char
El tipo de caracter.
function OnGUI() { var miEvento : Event = Event.current; if (miEvento.isKey) { Debug.Log("Pulsado caracter: " + miEvento.character); } } commandName var commandName : String El nombre de un evento de tipo ExecuteCommand o Validate Command ("Copy", "Cut", "Paste", "Delete", "FrameSelected", "Duplicate", "SelectAll", etc) keyCode: var keyCode : KeyCode El key code para eventos de teclado. Usado en los eventos EventType.KeyDown y EventType.KeyUp; devuelve el valor del KeyCode, por lo que se usa para manejar, por ejemplo, teclas de cursor, de funciones, etc. Teclead este código y tras salvar y darle al play pulsad por ejemplo una de las flechas de desplazamiento del teclado:
function OnGUI() { var miEvento : Event = Event.current; if (miEvento.isKey) { Debug.Log("El key code es: " + miEvento.keyCode); } }
shift: var shift : boolean
¿Está shift pulsado? (sólo lectura)
control: var control : boolean
¿Está control pulsado? (sólo lectura)
alt: var alt : boolean
¿Está alt pulsado? (Sólo lectura)
capsLock: var capsLock : boolean
¿Está el bloqueo de mayúsculas pulsado? (sólo lectura)
numeric: var numeric : boolean
¿Se está presionando alguna tecla del teclado numérico) (sólo lectura) functionKey: var functionKey : boolean ¿Es la tecla presionada una tecla de función (alt, ctrl, shift, etc)? (Sólo lectura) isKey: var isKey : boolean ¿Es este evento un evento de teclado? (sólo lectura) isMouse: var isMouse : boolean ¿Es este evento un evento de ratón? (sólo lectura)
105. CLASE EVENT (y III)
FUNCIONES: GetTypeFromControl: function GetTypeForControl (controlID : int) : EventType
Esta función devuelve un tipo de evento que es filtrado para un determinado control cuya id pasamos como parámetro. Esta función es usada para implementar bloqueos de ratón y de focos de teclado. El id del control para el que requerimos el tipo de evento se obtiene de GUIUtilty.GetControlID (), y en EventType podemos ver una lista de sus posibles valores.
Use: function Use () : void
Evento ya utilizado. deberíamos llamar a este método cuando ya hemos usado un evento. El tipo de evento será colocado en EventType.Used, causando que otros elementos GUI lo ignoren.
VARIABLES DE CLASE: current: static var current : Event
El evento actual/corriente que está siendo procesado en este mismo momento. Un ejemplo:
function OnGUI() { var miEvento : Event = Event.current; if(miEvento.type != EventType.repaint && miEvento.type != EventType.layout) { Debug.Log("Current detected event: " + Event.current); } }
Salvamos y tras pulsar al play disparamos los eventos que deseemos. Detenemos el reproductor y accedemos a la consola donde se muestran los mensajes haciendo click sobre el último y ahí tendremos toda la información sobre teclas pulsadas, movimientos y clics del ratón, etc. Observaréis que descarté la impresión de eventos de tipo repaint y layout, que son los que se producen de manera automática y en un número mayor.
106. CLASE GIZMOS
Los Gizmos son usados para permitir un debug visual o bien para colocar ayudas en la vista de escena. Todos los gizmos deben ser dibujados o con la función OnDrawGizmos o con la función. OnDrawGizmosSelected. La diferencia de ambas es que: OnDrawGizmos es llamada cada frame. OnDrawGizmosSelected es llamada sólo si el objeto al cual está vinculado el script es seleccionado.
VARIABLES DE CLASE: color: static var color : Color
Establece el color para los gizmos que serán dibujados a continuación. Vamos a hacer un pequeño ejemplo. Aseguráos de que MiPrimerScript sigue estando vinculado a la esfera, y acto seguido deseleccionar cualquier gameobject haciendo click en un espacio vacío de la Jerarquía. Comprobamos que en la vista game tengamos marcada la pestaña Gizmos. Escribimos:
function OnDrawGizmosSelected () { Gizmos.color = Color.blue; var direction : Vector3 = transform.TransformDirection (Vector3.forward) * 5; Gizmos.DrawRay (transform.position, direction); }
Le damos al play y no parece ocurrir nada. Esto es porque estamos llamando a la función OnDrawGizmosSelected, que sólo muestra el dibujo cuando seleccionamos el objeto al cual va vinculado el script, así que en la jerarquía seleccionamos la esfera y automáticamente nos debería aparecer una línea de color azul que va desde la posición del transform de la esfera cinco metros en adelante.
FUNCIONES DE CLASE: DrawRay: static function DrawRay (r : Ray) : void static function DrawRay (from : Vector3, direction : Vector3) : void Dibuja un rayo que empieza desde from hasta from + direction. Lo hemos visto en funcionamiento en el ejemplo anterior.
DrawWireSphere: static function DrawWireSphere (center : Vector3, radius : float) : void
Dibuja una esfera de alambre con centro y radio.
var radio = 2.0; function OnDrawGizmos() { Gizmos.color = Color.cyan; Gizmos.DrawWireSphere (transform.position, radio); }
Meramente como apunte, aquí la esfera de alambre se ve tengamos o no seleccionado el game object esfera, porque la hemos dibujado con la función OnDrawGizmos.
DrawSphere: static function DrawSphere (center : Vector3, radius : float) : void
Dibuja una esfera sólida con centro y radio.
DrawWireCube: static function DrawWireCube (center : Vector3, size : Vector3) : void
Dibuja una caja de alambre con centro y tamaño.
DrawCube: static function DrawCube (center : Vector3, size : Vector3) : void
Dibuja una caja sólida con centro y tamaño.
DrawIcon: static function DrawIcon (center : Vector3, name : String) : void Dibuja un icono en posición global en la vista de escena. El icono deberá tener el mismo nombre que le asignamos al parámetro name y estará ubicado en las coordenadas que le pasemos al parámetro center. El path del icono puede encontrarse en la carpeta Assets/Gizmos. Vamos por partes: Antes que nada necesitamos una imagen tipo icono. Yo para el ejemplo he usado ésta. Renombramos a la imagen como "nave". Por otro lado, en el archivo donde estamos guardando estos ejemplos, dentro de la carpeta assets, hemos de crear una carpeta llamada Gizmos, que es el path que Unity buscará para dar con los iconos de este tipo. Luego arrastramos nuestra nave a la recién creada carpeta. Y ahora editamos el script:
function OnDrawGizmos () { Gizmos.DrawIcon (transform.position + Vector3(0,2,0), "nave.png"); }
Pulsamos el play, y dos metros por encima de nuestra esfera debería aparecernos la nave, tal que así:
DrawGUITexture: static function DrawGUITexture (screenRect : Rect, texture : Texture, mat : Material = null) : void static function DrawGUITexture (screenRect : Rect, texture : Texture, leftBorder : int, rightBorder : int, topBorder : int, bottomBorder : int, mat : Material = null) : void
Dibuja una textura en coordenadas de pantalla. Es útil para backgrounds de GUI.
107. CLASE LIGHTMAPSETTINGS
Almacena los mapas de luces (lightmaps) de la escena. Una escena puede tener varios lightmaps almacenados en ella, y suss componentes Renderer pueden usar esos lightmaps. Esto hace posible usar el mismo material en múltiples objetos, mientras cada objeto puede referirse a diferentes lightmaps o diferentes porciones del mismo lightmap.
VARIABLES DE CLASE: lightmaps: static var lightmaps : LightmapData[]
Es un array de tipo LightmapData que puede almacenar diferentes lightmaps. LightmapData es una clase con dos variables:
lightmapFar: lightmapNear:
Lightmap que almacena la totalidad de la luz entrante. Lightmap que almacena sólo la luz indirecta entrante.
lightsmapMode: static var lightmapsMode : LightmapsMode
Modo de renderizado de lightmaps. LightmapsMode es una enumeración con los siguientes dos valores:
Single: Dual:
Modo de renderizado de lightmap tradicional. Modo de renderizado de lightmap dual.
108. ESTRUCTURA MATHF (I)
Estructura que contiene una colección de funciones matemáticas que podemos usar para nuestros scripts.
VARIABLES DE CLASE:
PI: static var PI : float El famoso 3.141592. (sólo lectura) Como pequeña anotación, observad que las variables de clase de Mathf comienzan por mayúscula, al contrario de las variables de clase del resto de clases y funciones en Unity. Tenedlo presente, ya que es una fuente importante de errores.
Infinity: static var Infinity : float Representación del infinito positivo (sólo lectura)
NegativeInfinity: static var NegativeInfinity : float
Una representación del infinito negativo (sólo lectura)
Deg2Rad: static var Deg2Rad : float
Conversión constante de grados a radianes (sólo lectura)
Rad2Deg: static var Rad2Deg : float
Conversión constante de radianes a grados (sólo lectura)
Epsilon: static var Epsilon : float
El valor más pequeño que un float puede tener diferente de cero (sólo lectura)
FUNCIONES DE CLASE: Sin: static function Sin (f : float) : float
Devuelve el seno del ángulo f en radianes.
Cos: static function Cos (f : float) : float Devuelve el coseno del ángulo f en radianes.
Tan: static function Tan(f : float) : float
Devuelve la tangente del ángulo f en radianes.
Asin: static function Asin (f : float) : float
Devuelve el arco seno de f menos el ángulo en radianes cuyo seno es f.
Acos: static function Acos (f : float) : float
Devuelve el arco coseno de f menos el ángulo en radianes cuyo coseno es f.
Atan: static function Atan (f : float) : float
Devuelve el arco tangente de f menos el ángulo en radianes cuya tangente es f.
Atan2: static function Atan2 (y : float, x : float) : float
Devuelve el ángulo en radianes cuya tangente es y/x. El valor retornado es el ángulo entre el eje X y un vector 2D que empieza en cero y acaba en (x,y)
Sqrt: static function Sqrt (f : float) : float
Devuelve la raíz cuadrada de f.
Abs: static function Abs (value : float) : float static function Abs (value : int) : int Devuelve el valor absoluto de value.
Min: static function Min (a : float, b : float) : float static function Min (params values : float[]) : float static function Min (a : int, b : int) : int static function Min (params values : int[]) : int Devuelve el valor mínimo de dos o más valores dados.
Max:
static function Max (a : float, b : float) : float static function Max (params values : float[]) : float static function Max (a : int, b : int) : int static function Max (params values : int[]) : int Devuelve el valor máximo de dos o más valores.
Pow: static function Pow (f : float, p : float) : float Devuelve f elevado a la potencia p.
Exp: static function Exp (power : float) : float
Devuelve la potencia natural de un determinado número.
Log: static function Log (f : float, p : float) : float static function Log (f : float) : float
Devuelve el logaritmo de un determinado número en una base especificada.
Log10: static function Log10 (f : float) : float
Devuelve el logaritmo en base diez de un determinado número.
109. ESTRUCTURA MATHF (II)
Ceil: static function Ceil (f : float) : float
Devuelve el integer más pequeño igual o mayor que f.
Floor: static function Floor (f : float) : float
Devuelve el mayor integer igual o más pequeño que f.
Round: static function Round (f : float) : float
Devuelve f redondeado al integer más cercano. Si el número acaba en .5 y queda entre dos integers, uno de los cuales es par y el otro impar, se devolverá el numero par.
CeilToInt: static function CeilToInt (f : float) : int
Devuelve el integer más pequeño igual o mayor que f.
FloorToInt: static function FloorToInt (f : float) : int
Devuelve el mayor integer menor o igual que f.
RoundToInt: static function RoundToInt (f : float) : int
Devuelve f rendondeada al integer más cercano. Si e número acaba en .5 y por lo tanto está a medio camino entre dos integers, uno impar y el otro par, se devuelve el número par.
Sign: static function Sign (f : float) : float
Devuelve el signo de f. Devuelve 1 si es positivo o cero, y -1 si f es negativo.
Clamp: static function Clamp (value : float, min : float, max : float) : float static function Clamp (value : int, min : int, max : int) : int Restringe un valor entre un mínimo y un máximo, sean floats o ints. Vamos a verlo con un ejemplo:
for(var x : int = 0; x <= 10; x++) { var numeroFijado : int = Mathf.Clamp(x, 1, 5); Debug.Log(numeroFijado); }
Mediante un bucle for le pasamos como primer parámetro a la función Clamp números del 10 al diez. Si desplegamos la consola tras probar este ejemplo veremos que cuando x vale 0, clamp devuelve el valor mínimo fijado (en este caso 1). Lo mismo pasa cuando x vale más de 5.
Clamp01: static function Clamp01 (value : float) : float Fija un valor entre 0 y 1 y lo devuelve.
Lerp: static function Lerp (from : float, to : float, t : float) : float
Interpola a hacia b pasando por t. t queda fijada entre 0 y 1. Cuando t = 0 devuelve from. Cuando t = 1 devuelve to. Cuando t = 0.5 devuelve la media de a y b.
LerpAngle: static function LerpAngle (a : float, b : float, t : float) : float
Lo mismo que lerp, pero asegurándonos de interpolar valores correctamente cuando dé un giro de 360 grados. Las variables a y b representan grados. Para ilustrar esta función y la anterior vamos a apañar un pequeño ejemplo. Sería interesante que eliminárais el script vinculado a la esfera. Editamos:
var var var var
origen = -2.0; destino = 1.0; anguloInicial= 0.0; anguloFinal= 90.0;
function Update () { transform.position = Vector3(Mathf.Lerp(origen, destino, Time.time * 0.1), 0, 0); var angle : float = Mathf.LerpAngle(anguloInicial, anguloFinal, Time.time * 0.1); transform.eulerAngles = Vector3(0, angle, 0); }
Salvamos y vinculamos el script al cubo. A la función Lerp le pasamos el parámetro de situación inicial -2, que coincide con su posición actual en el eje x, y le indicamos un destino en 1. Tradicionalmente como tercer parámetro se coloca Time.time, que pasa de 0 a 1 en un segundo, pero como queremos que el trayecto sea más lento, multiplicamos Time.time por 0.1, de tal manera que el trayecto total dure 10 segundos. Otro tanto hacemos con el angulo inicial y final. Como resultado, el cubo se moverá tres metros en el eje X y girará 90 grados sobre el eje Y en diez segundos.
MoveTowards: static function MoveTowards (current : float, target : float, maxDelta : float) : float
Mueve el valor que indicamos en el parámetro current hacia el que indicamos en target. Hasta aquí la función sería parecida a Mathf.Lerp, pero aquí nos aseguramos de la que velocidad no exceda de maxDelta. Valores negativos para maxDelta empuja el valor lejos del objetivo.
MoveTowardsAngle:
static function MoveTowardsAngle (current : float, target : float, maxDelta : float) : float
Lo mismo que MoveTowards pero estando seguros de que los valores se interpolarán correctamente cuando gire 360 grados. La diferencia de MoveTowards y MoveTowardsAngle con respecto de Lerp y LerpAngle la veremos más fácilmente rehaciendo el script anterior:
var var var var
origen = -2.0; destino = 1.0; minAngle = 0.0; maxAngle = 90.0;
function Update () { transform.position = Vector3(Mathf.MoveTowards(origen, destino, Time.time * 1), 0,0); var angle : float = Mathf.MoveTowardsAngle(minAngle, maxAngle, Time.time * 30); transform.eulerAngles = Vector3(0, angle, 0); }
La diferencia la tenemos que hallar en el tercer parámetro. En el caso de MoveTowards lo que le estamos pidiendo aquí al cubo es que se mueva en el eje x de la posición -2 a la 1 (3 metros, o sea) a razón de un metro por segundo. Y Para MoveTowardsAngle estamos indicándole al cubo que gire 90 grados a razón de 30 grados por segundo. De esta manera, en 3 segundos el cubo debería haber completado ambos movimientos. Probadlo.
SmoothStep: static function SmoothStep (from : float, to : float, t : float) : float Interpola entre mínimo y máximo y facilita entrada y salida de los límites. Sería como Lerp, pero con un impulso inicial. Si meramente sustituís em el último script MoveTowards por SmoothStep veréis a qué me refiero.
Approximately: static function Approximately (a : float, b : float) : boolean Compara si dos valores en punto flotante son similares. Debido a que los numeros en punto flotante son imprecisos no es recomendable compararlos usando el operador == (podría no devolver true), y es mejor utilizar esta función.
110. ESTRUCTURA MATHF (y III)
SmoothDamp: static function SmoothDamp (current : float, target : float, ref currentVelocity : float, smoothTime : float,
maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float
Gradualmente cambia un valor hacia un objetivo en un determinado tiempo. La función se puede usar para suavizar la transición de valores, colores, posiciones, escalares…. Cuenta con los siguientes parámetros:
current target currentVelocity cada smoothTime objetivo. maxSpeed deltaTime
La posición actual. La posición que estamos tratando de alcanzar. La velocidad actual. Este valor es modificado por la función vez que la llamamos. Aproximadamente el tiempo que tardaremos en alcanzar el Un valor pequeño hará que allcancemos el objetivo más rápido. Opcionalmente nos permite fijar la velocidad máxima. El tiempo desde la última llamada a esta función. Por defecto Time.deltaTime.
Un ejemplo:
var objetivo : Transform; var tiempoEmpleado = 3; private var yVelocity =4.0; function Update () { var newPosition : float = Mathf.SmoothDamp(transform.position.x, objetivo.position.x, yVelocity, tiempoEmpleado); transform.position = Vector3(newPosition, transform.position.y, transform.position.z); }
Salvamos y arrastramos el cubo a la variable expuesta "objetivo". Lo que aquí estamos haciendo es usar la función SmoothDamp para ir marcando la nueva posición de nuestro cubo. El punto de origen será la posición actual del cubo (en el eje x), el destino la posición actual en dicho eje del transform que marcamos como objetivo, le establecemos un tiempo para que el origen alcance al objetivo de 3 segundos y le limitamos la velocidad máxima que pueda alcanzar nuestro cubo a cuatro metros por segundo.
SmoothDampAngle: static function SmoothDampAngle (current : float, target : float, ref currentVelocity : float, smoothTime : float, maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float
Cambia gradualmente un ángulo dado en grados hacia el ángulo que constituye el objetivo en un tiempo determinado. El uso más común de esta función es para suavizar una cámara que esté siguiendo algún personaje o escena.
Repeat: static function Repeat (t : float, length : float) : float
Introduce en un bucle el valor t, de tal manera que nunca sea más grande que length y nunca más pequeño que 0.
PingPong: static function PingPong (t : float, length : float) : float
Hace rebotar el valor t, de tal manera que nunca sea mayor que length y nunca mayor que 0. El valor retornado se moverá atrás y adelante entre 0 y length.
InverseLerp: static function InverseLerp (from : float, to : float, value : float) : float
Calcula el parámetro Lerp entre dos valores.
ClosestPowerOfTwo: static function ClosestPowerOfTwo (value : int) : int
Retorna la potencia de dos más cercana.
IsPowerOfTwo: static function IsPowerOfTwo (value : int) : boolean
Devuelve true si el valor es potencia de dos
NextPowerOfTwo: static function NextPowerOfTwo (value : int) : int Devuelve el valor de la siguiente potencia de dos.
DeltaAngle: static function DeltaAngle (current : float, target : float) : float
Calcula la diferencia más corta entre dos ángulos dados.
111. CLASE PHYSICS (I)
Componen esta clase, al igual que la estructura Mathf anterior, propiedades globales y métodos de ayuda relacionados con las físicas. VARIABLES DE CLASE: gravity: static var gravity : Vector3
La gravedad aplicada a todos los rigidbodies en la escena. Puede ser desconectada para un rigidbody individual usando su propiedad useGravity.
FUNCIONES DE CLASE: Raycast: static function Raycast (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Lanza un rayo contra todos los colliders en la escena. Devuelve true cuando el rayo intersecta algún collider. Tiene los siguientes parámetros:
origin direction distance layerMask
El punto inicial del rayo en coordenadas globales. La dirección del rayo. La longitud o fuerza del rayo. Una máscara de distribución (Layer mask) que se usa para ignorar selectivamente colliders cuando se proyecta un rayo.
Por ejemplo: function Update () { var derecha : Vector3 = transform.TransformDirection (Vector3.right); if (Physics.Raycast (transform.position, derecha, 10)) { print ("Hay algo a mi derecha"); } } Lo que hacemos aquí es primero tomar la dirección local de nuestro cubo y convertirla en dirección global, a través de la función TransformDirection. Dicha dirección global la almacenamos en la variable "derecha". Acto seguido, imprimimos un mensaje si desde la posición de nuestro cubo en una distancia no superior a diez metros hay a la derecha global del mismo otro objeto. static function Raycast (origin : Vector3, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Proyecta un rayo contra todos los colliders en la escena y devuelve información detallada sobre qué golpeó. Los parámetros de este segundo prototipo de función son:
origin direction distance hitInfo sobre layerMask cuando se
El punto de inicio del rayo en coordenadas globales. La dirección del rayo. La fuerza o longitud del rayo. Si se devuelve true, esta variable contendrá más información donde colisionó el collider. Un layer mask usado para ignorar colliders selectivamente proyecte un rayo.
static function Raycast (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean static function Raycast (ray : Ray, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Son similares a las funciones anteriores, sólo que usando ray.origin y ray.direction en vez de origen y dirección como sendos Vector3.
RaycastAll: static function RaycastAll (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : RaycastHit[] static function RaycastAll (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layermask : int = kDefaultRaycastLayers) : RaycastHit[] Lanza un rayo a través de la escena y devuelve todos los choques. Vamos a adaptar un ejemplo del manual de referencia:
function Update () { var choques : RaycastHit[]; choques = Physics.RaycastAll (transform.position, transform.right, 100.0); for (var i = 0;i < choques.Length; i++) { var miChoque : RaycastHit = choques[i]; var renderer = miChoque.collider.renderer; if (renderer) { renderer.material.shader = Shader.Find("Transparent/Diffuse"); renderer.material.color.a = 0.3; } } } Vamos paso a paso en la explicación. Primero declaramos una variable que contendrá un array de tipo RaycastHit, que es precisamente lo que hemos visto que devuelve la función RaycastAll. La inicializamos con todas aquellas colisiones que sufra nuestro rayo, el cual proyectamos desde el cubo 100 metros a la derecha. Dado que puede haber más de una colisión, iteramos a través del array y el collider con el que se ha producido cada colisión le es asignado temporalmente a la variable miChoque, a través de la cual lo volvemos semitransparente. Si pulsamos play vemos que nuestra esfera se torna semiinvisible. LineCast: static function Linecast (start : Vector3, end : Vector3, layerMask : int = kDefaultRaycastLayers) : boolean Devuelve true si hay algún collider intersectando la línea entre start y end. static function Linecast (start : Vector3, end : Vector3, out hitInfo : RaycastHit, layerMask : int = kDefaultRaycastLayers) : boolean
En este segundo prototipo, si se devuelve true, hitinfo contendrá más información sobre dónde colisionó el collider.
OverlapSphere: static function OverlapSphere (position : Vector3, radius : float, layerMask : int = kAllLayers) : Collider[]
Devuelve un array con todos los colliders que toquen o estén dentro de la esfera cuya posición y radio pasamos como parámetros.
112. CLASE PHYSICS (y II)
CapsuleCast: static function CapsuleCast (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean static function CapsuleCast (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Lanza una cápsula contra todos los colliders de la escena y devuelve información de contra qué ha chocado. Devuelve true cuando el sweep (barrido) de la cápsula choca con algún collider. Los parámetros de esta función son:
point1 point2 radius direction hitInfo golpeó
El El El La Si
inicio de la cápsula. fin de la cápsula. radio de la cápsula. dirección en la cual hace el barrido la cápsula. devuelve true, hitInfo contendrá más información sobre dónde
el collider. distance layerMask cuando
La longitud del barrido. Un Layer mask que se usa para ignorar selectivamente colliders se proyecte la cápsula.
La cápsula viene conformada por las dos esferas con radios alrededor del point1 y point2, que forman los dos finales de la cápsula. Esto es útil cuando un Raycast no tiene suficiente precisión para lo que queremos hacer, como por ejemplo asegurarnos de que un personaje podrá moverse a cualquier sitio sin colisionar con nada en el camino.
SphereCast: static function SphereCast (origin : Vector3, radius : float, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean
Proyecta una esfera contra todos los colliders de la escena y proporciona información sobre los que colisionan. Devuelve true si topa con alguno en su barrido. Cuenta con estos parámetros:
origin radius direction hitInfo información distance layerMask cuando
El El La Si
centro de la esfera al principio del barrido. radio de la esfera. dirección en la cual hace el barrido la esfera. la función devuelve true, esta variable contendrá más
acerca de dónde el collider colisionó (Para más información ver: RaycastHit). La longitud del barrido. Un Layer mask que se usa para ignorar selectivamente colliders se proyecta la cápsula.
Esta función es útil cuando un Raycast no nos da suficiente precisión, como por ejemplo en el caso en que queramos averiguar si un objeto de un determinado tamaño, como un character, será capaz de de moverse a algún lado sin colisionar con algo por el camino. En casos como estos es preferible usar la función SphereCast. Hemos de tener presente, eso sí, que la SphereCast no funcionará contra aquellos colliders configurados como triggers. Probemos un ejemplo. Previamente hay que añadirle un character controller al cubo. Acto seguido tecleamos este script:
function Update () { var hit : RaycastHit; var charCtrl : CharacterController = GetComponent(CharacterController); var p1 : Vector3 = transform.position + charCtrl.center; if (Physics.SphereCast (p1, charCtrl.height /2, transform.right, hit, 10)) { Debug.Log("Hay un obstáculo a " + hit.distance + " metros"); } }
Lo que hemos hecho aquí es, a grandes rasgos, que nuestro cubo efectúe un barrido hacia la derecha en busca de obstáculos para un character con un radio equivalente a la mitad de la altura de nuestro character. Al topar con uno, se muestra un mensaje en pantalla junto con información suplementaria, como en este caso la distancia a que se halla dicho obstáculo. El ejemplo es un poco rupestre, pero nos permite intuir la utilidad de esta función para aplicársela a un personaje. static function SphereCast (ray : Ray, radius : float, distance : float Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean static function SphereCast (ray : Ray, radius : float, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean
CapsuleCastAll: static function CapsuleCastAll (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layermask : int = kDefaultRaycastLayers) : RaycastHit[]
Es como Physics.CapsuleCast, pero devolviendo todos los colliders que la cápsula intercepta en su barrido e información sobre los mismos. Devuelve un array con todos esos colliders. Parámetros:
point1 point2 radius direction distance layerMask
El principio de la cápsula. El final de la cápsula. El radio de la cápsula. La dirección en la cual efectúa el barrido la cápsula. La longitud del barrido. Un Layer mask que se usa para ignorar selectivamente algunos colliders cuando se proyecta la cápsula
La cápsula es definida por las dos esferas con su radio alrededor de point1 y point2, que forman los dos extremos de la cápsula. Son devueltos datos de todos los colliders contra los que nuestra cápsula proyectada choque en esa dirección. Recordemos que esta función no funciona contra colliders configurados como triggers.
SphereCastAll: static function SphereCastAll (origin : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : RaycastHit[] static function SphereCastAll (ray : Ray, radius : float, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : RaycastHit[] Como physics.SphereCast, pero devolviendo todos los colliders que colisionen.
CheckSphere: static function CheckSphere (position : Vector3, radius : float, layerMask : int = kDefaultRaycastLayers) : boolean
Devuelve true si hay algún collider tocando la esfera definida por position y radius en coordenadas globales.
CheckCapsule: static function CheckCapsule (start : Vector3, end : Vector3, radius : float, layermask : int = kDefaultRaycastLayers) : boolean
Devuelve true si hay algún collider tocando la cápsula definida por el eje que va de start a end y que tiene el radio radius, en coordenadas globales.
IgnoreCollision: static function IgnoreCollision (collider1 : Collider, collider2 : Collider, ignore : boolean = true) : void
Hace que el sistema de detección de colisiones ignore todas las colisiones entre collider1 y collider2. Esto es muy útil por ejemplo para que los proyectiles no colisionen con el objeto que los dispara.
Esta función tiene algunas limitaciones: 1.- No es persistente. Esto significa que el estado de ignorar colisión no será almacenado en el editor cuando se salve la escena. 2.- Sólo puedes aplicar esta función a colliders en gameobjects activos. Cuando se desactiva el collider o el rigidbody vinculado se pierde el estado de IgnoreCollision y tendrás que llamar a esta función otra vez.
IgnoreLayerCollision: static function IgnoreLayerCollision (layer1 : int, layer2 : int, ignore : boolean = true) : void
Hace que el sistema de detección de colisiones ignore todas las colisiones entre cualquier collider en layer1 y otros en layer2.
GetIgnoreLayerCollision: static function GetIgnoreLayerCollision (layer1 : int, layer2 : int) : boolean
Booleano que indica si las colisiones entre layer1 y layer2 están siendo ignoradas.
113. ESTRUCTURA QUATERNION
Nuestros amigos los quaterniones son usados para representar rotaciones. Unity internamente usa Quaterniones para representar todas las rotaciones. Sin embargo, los quaterniones están basados en números complejos y no son fáciles de entender intuitivamente, así que casi nunca accederemos o modificaremos los componentes individuales de un Quaternión (x,y,z,w). Lo que será más habitual es que queramos meramente tomar rotaciones ya existentes (por ej desde un Transform) y usarlas para construir nuevas rotaciones (por ej interpolando suavemente entre dos rotaciones). Las funciones sobre Quaterniones que usaremos el 99% del tiempo (las otras funciones son para usos exóticos) son Quaternion.LookRotation, Quaternion.Angle, Quaternion.Euler, Quaternion.Slerp, Quaternion.FromToRotation y Quaternion.identity.
VARIABLES: eulerAngles: var eulerAngles : Vector3
Devuelve la representacion en ángulos euler de la rotacion. FUNCIONES: ToAngleAxis: function ToAngleAxis (out angle : float, out axis : Vector3): void Convierte una rotación en una representación de angle-axis (ángulo de eje) SetFromToRotation: function SetFromToRotation (fromDirection : Vector3, toDirection : Vector3) : void Crea una rotación que rota desde fromDirection hasta toDirection. ToString: function ToString () : String function ToString (format : String) : String Devuelve un string formateado del Quaternion. VARIABLES DE CLASE: identity:
static var identity : Quaternion Sería el equivalente a no rotation. El transform quedará perfectamente alineado con el mundo o con los ejes del padre. FUNCIONES DE CLASE: AngleAxis: static function AngleAxis (angle : float, axis : Vector3) : Quaternion
Crea una rotación que rota los ángulos que le pasemos como primer parámetro con respecto al eje que le pasamos como segundo. O sea, para girar nuestro cubo (previamente eliminadle el character controller que le añadimos el capítulo anterior), tecleamos:
transform.rotation = Quaternion.AngleAxis(30, Vector3.up);
De esta forma tan simple giramos el cubo 30 grados sobre el eje Y.
FromToRotation: static function FromToRotation (fromDirection : Vector3, toDirection : Vector3) : Quaternion Crea una rotación que rota desde el primer parámetro al segundo. Normalmente usaremos esta función para rotar un transform uno de cuyos ejes sigue un objetivo en una dirección en coordenadas globales.
Slerp: static function Slerp (from : Quaternion, to : Quaternion, t : float) : Quaternion
Interpola esfériamente del primer al segundo parámetro durante el tercero.
RotateTowards: static function RotateTowards (from : Quaternion, to : Quaternion, maxDegreesDelta : float) : Quaternion
Efectúa una rotación entre el primer parámetro y el segundo. Esto es esencialmente lo mismo que Quaternion.Slerp, pero aquí la función se aregura de que la velocidad angular nunca exceda de la marcada en maxDegreesDelta. Si maxDegreesDelta tiene un valor negativo la rotación es empujada lejos del segundo parámetro (to).
Angle: static function Angle (a : Quaternion, b : Quaternion) : float
Devuelve el ángulo en grados entre dos rotaciones dadas. Veamos eso:
var objetivo : Transform; function Update () { var angulo : float = Quaternion.Angle(transform.rotation, objetivo.rotation); Debug.Log(angulo); }
Arrastramos la esfera a la variable expuesta objetivo. Si pulsamos play, observaremos que nos aparece un cero en pantalla, ya que (si teneis ambas figuras colocadas como yo) ambas tienen sus respectivas rotaciones a 0,0,0. Probad ahora a cambiar la rotación de una -o las dos- figuras.
Euler: static function Euler (x : float, y : float, z : float) : Quaternion static function Euler (euler : Vector3) : Quaternion Devuelve en quaterniones una rotación pasada como parámetro en grados euler.
114. CLASE RANDOM
Clase para generar números aleatorios.
VARIABLES DE CLASE: seed: static var seed : int
Coloca la semilla para el generador de números aleatorios.
value: static var value : float
Devuelve un número aleatorio entre 0.0 (inclusive) y 1.0 (inclusive).
for(var x: int =0; x<10; x++){ print(Random.value); }
Podréis comprobar que los diez números que nos aparecerán en pantalla están entre ambos valores.
insideUnitSphere: static var insideUnitSphere : Vector3
Devuelve un punto aleatorio dentro de una esfera con radio 1.
transform.position = Random.insideUnitSphere * 2;
Este ejemplo situaría nuestro cubo en un punto aleatorio dentro de una esfera (3 dimensiones) con un radio de 2 unidades.
insideUnitCircle: static var insideUnitCircle : Vector2
Devuelve un punto aleatorio dentro de un círculo con radio 1.
var newPosition : Vector2 = Random.insideUnitCircle * 5; transform.position.x = newPosition.x; transform.position.y = newPosition.y;
en este caso nuestro cubo se movería dentro de un círculo (2D) con radio de 5 unidades.
onUnitSphere: static var onUnitSphere : Vector3 Devuelve un punto aleatorio sobre la superficie de una esfera con radio 1.
function FixedUpdate(){ rigidbody.velocity = Random.onUnitSphere * 10; }
Esta función mueve al rigidbody de nuestro cubo a una velocidad de 10 en una dirección aleatoria, por lo que no esperéis ver otra cosa al darle al play que un cubo volviéndose loco.
rotation: static var rotation : Quaternion
Devuelve una rotación aleatoria (read only)
var prefab : GameObject; Instantiate(prefab, Vector3.zero, Random.rotation);
Este ejemplo instanciaría un nuevo gameobject en el centro de la escena y con una rotación aleatoria.
FUNCIONES DE CLASE: Range:
static function Range (min : float, max : float) : float
Devuelve un float aleatorio entre un min (inclusive) y max (inclusive).
var prefab : GameObject; function Start () { var position: Vector3 = Vector3(Random.Range(-5.0, 5.0), 0, Random.Range(5.0, 5.0)); Instantiate(prefab, position, Quaternion.identity); }
Si arrastramos la esfera a la variable expuesta prefab, al darle al play observaremos que se clona una instancia de la misma y aparece en un lugar aleatorio en un margen de 5 metros en los ejes X y Z. static function Range (min : int, max : int) : int La misma función, pero admite y devuelve integers.
115. ESTRUCTURA RAY
Estructura que nos permite representar y modificar rayos. Un rayo es una linea infinita que empieza en un punto dado y va en alguna dirección.
VARIABLES: origin: var origin : Vector3
El punto de origen del rayo.
direction: var direction : Vector3
La dirección del rayo. La dirección es siempre un vector normalizado(1,0,0 o 0,1,0 o 0,0,1. Si asignamos un vector de longitud distinta de la unidad, será normalizado.
FUNCIONES: Ray: static function Ray (origin : Vector3, direction : Vector3) : Ray
Crea un rayo que empieza en origin a lo largo de direction.
var ray = new Ray (transform.position, transform.forward);
En este ejemplo crearíamos un rayo que parte de la posición del transform al que está vinculado el script y que parte hasta el infinito a través del eje Z.
GetPoint: function GetPoint (distance : float) : Vector3 Devuelve un punto tantas unidades como le pasemos en el parámetro a lo largo del rayo.
var r : Ray; print( r.GetPoint (10) ); Este ejemplo imprime un punto situado 10 unidades a lo largo del rayo. ToString: function ToString () : String function ToString (format : String) : String Devuelve un string formateado para este rayo.
116. ESTRUCTURA RAYCASTHIT
Estructura usada para obtener información de vuelta de un raycast (rayo proyectado).
VARIABLES: point: var point : Vector3
El punto de impacto en coordenadas globales donde el rayo golpea el collider
normal: var normal : Vector3
El normal de la superficie que golpea el rayo.
baryentricCoordinate:
var barycentricCoordinate : Vector3
La coordenada baricéntrica del triángulo que golpeamos (baricentro = es un punto de una figura geométrica la recta que pasa por el cual divide la figura en dos partes iguales. ) Esto nos permite interpolar cualquiera de los datos de los vértices a lo largo de los tres ejes.
distance: var distance : float
La distancia desde el origen del rayo hasta el punto de impacto.
triangleIndex: var triangleIndex : int
el índice del triángulo que ha sido golpeado. El índice del triángulo es sólo válido si el colider que lo golpea es un MeshCollider.
textureCoord: var textureCoord : Vector2
La coordenada de la textura UV en el punto de impacto. Esto puede ser usado para pinturas de textura 3d o impactos de bala dibujados. Si el collider no es una mesh collider, retorna un Vector2 a cero.
textureCoord2: var textureCoord2 : Vector2
Las coordenadas de la textura uv secundaria.
lightmapCoord: var lightmapCoord : Vector2
La coordinada del lightmap de uv en el punto de impacto.
colider: var collider : Collider
El collider que fue golpeado. Esta propiedad es nula si no se golpea nada y no-nula si golpeas algo.
rigidbody: var rigidbody : Rigidbody
El rigidbody del collider que ha sido golpeado. Si el collider no está vinculado a un rigidbody es null.
transform: var transform : Transform
El Transform del rigidbody o collider que ha sido golpeado.
RESUMEN DE LA API DE UNITY Behaviour VARIABLES:
enabled: var enabled : boolean Habilita/deshabilita el objeto Behaviour (y/o los objetos derivados de éste).
Bounds VARIABLES:
center: var center : Vector3 El centro de la caja. size: var size : Vector3 El tamaño total de la caja. Es siempre dos veces más grande que las extensiones (extents) extents: var extents : Vector3 Las extensiones de la caja. Es siempre la mitad del tamaño (size) min: var min : Vector3 El punto mínimo de la caja. Es siempre igual a center – extents. max: var max : Vector3 El punto máximo de la caja. Siempre es igual a center+extents.
FUNCIONES:
Bounds: static function Bounds (center : Vector3, size : Vector3) : Bounds Crea una nueva caja de bordes con un centro dado y un tamaño total. Las extensiones deben ser la mitad del tamaño dado. SetMinMax: function SetMinMax (min : Vector3, max : Vector3) : void Establece los bordes en los valores mínimos y máximos de la caja. Usar esta función es más rápido que asignar min y max de manera separada. Encapsulate: function Encapsulate (point : Vector3) : void Incrementa la caja para incluir el punto que se pasa como parámetro. Expand: function Expand (amount : float) : void Expande la caja para incrementar su tamaño en una cantidad a lo largo de cada cara. Intersects: function Intersects (bounds : Bounds) : boolean ¿Hay alguna otra caja intersectando nuestra caja? Contains: function Contains (point : Vector3) : boolean ¿Está el punto point contenido en la caja? sqrDistance: function SqrDistance (point : Vector3) : float El cuadrado de la distancia entre el punto point y la caja. IntersectRay: function IntersectRay (ray : Ray) : boolean ¿El rayo ray intersecta esta caja?
Camera VARIABLES:
fieldOfView: var fieldOfView : float Variable que contiene el campo de visión de la cámara en grados. nearClipPlane: var nearClipPlane : float El plano de recorte de cerca. Cualquier cosa que se halle más cerca de la cámara de la distancia establecida en esta variable no se mostrará en la cámara.
farClipPlane: var farClipPlane : float El plano de recorte de lejos. Cualquier cosa que se halle más lejos de la cámara de la distancia establecida en esta variable no se mostrará en la cámara. renderingPath: var renderingPath : RenderingPath Indica el tipo de renderizado de entre los que contempla la enum RenderingPath. actualRenderingPath: var actualRenderingPath : RenderingPath Variable de sólo lectura que contiene el rendering path que se está usando. orthographicSize: var orthographicSize : float El campo de visión de la cámara cuando está en modo ortográfico. orthographic: var orthographic : boolean Variable de tipo booleano que indica si la cámara está en modo ortográfico (true) o en perspectiva (false) y que permite pasar de uno a otro. depth: var depth : float La profundidad de la cámara en el orden de renderizado de las cámaras. Las cámaras con profundidad más baja son renderizadas antes de las cámaras con profundidad más alta. aspect: var aspect : float Variable en la que se guarda/coloca la proporción de aspecto (aspect ratio), que es el nombre con que se conoce a la anchura dividida por la altura de la cámara. cullingMask: var cullingMask : int Es usada para renderizar de manera selectiva partes de la escena. backgroundColor: var backgroundColor : Color Variable que indica el color con el cual la pantalla será completada. rect: var rect : Rect Establece qué parte de la pantalla esta la cámara renderizando. pixelRect var pixelRect : Rect Indica/establece qué parte de la pantalla esta la cámara renderizando, pero a diferencia de rect, no lo indica en coordenadas normalizadas, sino en píxeles. pixelWidth: var pixelWidth : float
Indica la anchura de la cámara en píxeles. (Sólo lectura). pixelHeight: var pixelHeight : float Esta variable indica la altura de la cámara en píxeles(sólo lectura) velocity: var velocity : Vector3 Variable de sólo lectura que indica la velocidad de la cámara en el espacio global. clearFlags: var clearFlags : CameraClearFlags Indica cómo la cámara completa el background.
FUNCIONES:
ResetAspect: unction ResetAspect () : void Revierte el aspect ratio de la cámara al aspect ratio de la pantalla, acabando con el efecto de modificar la variable aspect. WorldToScreenPoint: function WorldToScreenPoint (position : Vector3) : Vector3 Transforma la posición de un transform desde el espacio global al espacio de la pantalla. WorldToViewportPoint: function WorldToViewportPoint (position : Vector3) : Vector3 Convierte la posición de un transform desde las coordenadas globales al espacio de punto de vista. ViewportToWorldPoint: function ViewportToWorldPoint (position : Vector3) : Vector3 Convierte la posición de un transform medida en el viewport space relativo de la cámara a coordenadas globales. ScreenToWorldPoint: function ScreenToWorldPoint (position : Vector3) : Vector3 Convierte la posición de un transform desde el espacio de pantalla en píxeles a coordenadas globales. ScreenToViewportPoint: function ScreenToViewportPoint (position :
Vector3) : Vector3 Convierte la posición de un transform de espacio de pantalla en píxeles a viewport space relativo al espacio de cámara. ViewportToScreenPoint: function ViewportToScreenPoint (position : Vector3) : Vector3 Convierte la posición del transform de viewport space relativo al espacio de cámara en espacio de pantalla en píxeles. ViewportPointToRay: function ViewportPointToRay (position : Vector3) : Ray Devuelve un rayo que sale de la cámara en coordenadas relativas a ésta. ScreenPointToRay: function ScreenPointToRay (position : Vector3) : ray Devuelve un rayo que va de la cámara a través de un punto de la pantalla. Render: function Render () : void Renderiza la cámara manualmente, usando el clear flags de la cámara, target texture y otras propiedades. RenderWithShader: function RenderWithShader (shader : Shader, replacementTag : String) : void Hace que la cámara renderice con reemplazo de sombreado (shader) . SetReplacementShader: function SetReplacementShader (shader : Shader, replacementTag : String) : void Hace que la cámara renderice con shader replacement. ResetReplacementShader: function ResetReplacementShader () : void Borra el shader replacement de la cámara provocado por la función anterior. RenderToCubemap: function RenderToCubemap (cubemap : Cubemap, faceMask : int = 63) : boolean Renderiza un cubemap desde la cámara que llama a esta función. CopyFrom: function CopyFrom (other : Camera) : void Permite copiar para una cámara todas las variables de otra cámara (campo de vision, clear flags, culling mask…).
VARIABLES DE CLASE:
main: static var main : Camera Variable de sólo lectura que se refiere a la cámara que esté habilitada y con el tag “Main Camera”. allCameras: static var allCameras : Camera[] Devuelve todas las cámaras habilitadas en la escena.
CharacterController VARIABLES:
isGrounded: var isGrounded : boolean Bool que devuelve true si nuestro character controller estaba tocando el suelo durante el último movimiento. velocity: var velocity : Vector3 Esta variable nos indicá qué parte de la cápsula de nuestro character controller colisionó con el entorno durante la última llamada a CharacterController.Move. radius: var radius : float El radio de la cápsula del character controller. height: var height : float La altura de la cápsula del character controller. center: var center : Vector3 El centro de la cápsula del character relativo a la posición del transform. slopeLimit: var slopeLimit : float El límite de pendiente en grados por el que puede ascender nuestro character controller. stepOffset: var stepOffset : float Los límites de altura que el character controller podrá superar, fijados en metros. detectCollisions: var detectCollisions : boolean
Determina si otros rigidbodies o character controllers colisionan con nuestro character Controller
FUNCIONES:
Move: function Move (motion : Vector3) : CollisionFlags Esta función mueve un character controller en la dirección y velocidad establecida por el parámetro de tipo vector tres. Dicho movimiento sólo será restringido por las colisiones que sufra el character. SimpleMove: function SimpleMove (speed : Vector3) : boolean Mueve un character controller con una velocidad dada y asignándole gravedad. CharacterJoint VARIABLES:
swingAxis: var swingAxis : Vector3 El eje secundario alrededor del cual el joint puede rotar. lowTwistLimit: var lowTwistLimit : SoftJointLimit El límite menor del ángulo permitido alrededor del eje primario del characterjoint. highTwistLimit: var highTwistLimit : SoftJointLimit El límite superior altededor del eje primario del characterjoint. swing1Limit: var swing1Limit : SoftJointLimit El límite alrededor del eje primario del characterjoint. swing2Limit: var swing2Limit : SoftJointLimit El límite alrededor el eje primario del joint del character.
Collider VARIABLES:
enabled: var enabled : boolean Si el collider está habilitado (true), colisionará con otros colliders.
attachedRigidbody: var attachedRigidbody : Rigidbody Esta variable hace referencia al rigidbody vinculado a este collider, y permite acceder a él. Devuelve nulo si el collider no tiene rigidbody. isTrigger: var isTrigger : boolean Si esta variable está habilitada (true) el collider se convierte en un trigger (lo podríamos traducir por "desencadenante" o "disparador"). Un trigger no colisiona con rigidbodies, y en su lugar envía los mensajes OnTriggerEnter, OnTriggerExit y OnTriggerStay. material: var material : PhysicMaterial El material usado por el collider. sharerMaterial: var sharedMaterial : PhysicMaterial El material compartido de este collider. Modificando este material cambiaremos las propiedades de la superficie de todos los colliders que estén usando el mismo material. bounds: var bounds : Bounds Los límites/bordes del collider en coordenadas globales.
FUNCIONES:
ClosestPointOfBounds: function ClosestPointOnBounds (position : Vector3) : Vector3 Devuelve el punto más cercano de nuestro Collider con respecto a un punto dado. Raycast: function Raycast (ray : Ray, hitInfo : RaycastHit, distance : float) : boolean Proyecta un rayo que devuelve true si tropieza con algún collider en la dirección y distancia indicadas. En este caso, además, nos provee de información sobre dicho collider. OnTriggerEnter: function OnTriggerEnter (other : Collider) : void Es llamada cuando nuestro trigger entra en contacto con el collider "other". OnTriggerExit: function OnTriggerExit (other : Collider) : void Es llamada cuando el collider "other" deja de tocar con nuestro trigger.
OnTriggerStay: function OnTriggerStay (other : Collider) : void Es llamada casi todos los frames que el collider other esté tocando nuestro trigger. OnCollisionEnter: function OnCollisionEnter (collisionInfo : Collision) : void Es llamada cuando nuestro collider/rigidbody empiece a tocar otro rigidbody/collider. OnCollisionExit: function OnCollisionExit (collisionInfo : Collision) : void Es llamada cuando nuestro collider/rigidbody deja de tocar otro rigidbody/collider. OnCollisionStay: function OnCollisionStay (collisionInfo : Collision) : void Es llamada una vez por frame por cada collider/rigidbody que esté tocando nuestro rigidbody/collider.
Collision VARIABLES:
relativeVelocity: var relativeVelocity : Vector3 Variable de sólo lectura que devuelve la velocidad lineal relativa de los dos objetos que colisionan. rigidbody: var rigidbody : Rigidbody Variable de sólo lectura que hace referencia al rigidbody que golpeamos. collider: var collider : Collider El collider que golpeamos (sólo lectura). transform: var transform : Transform El transform del objeto que golpeamos (read only). gameObject: var gameObject : GameObject Variable de sólo lectura que devuelve el objeto con el que chocamos. contacts: var contacts : ContactPoint[] El punto de contacto generado por el engine de físicas.
ControllerColliderHit VARIABLES:
controller: var controller : CharacterController El controller que golpea el collider. collider: var collider : Collider El collider que fue golpeado por el controller. rigidbody: var rigidbody : Rigidbody El rigidbody que fue golpeado por el controller. gameObject: var gameObject : GameObject El game object que fue golpeado por el controller. transform: var transform : Transform El transform que fue golpeado por el controller. point: var point : Vector3 El punto de impacto en coordenadas globales. normal: var normal : Vector3 El normal de la superficie con la que colisionamos en coordenadas globales. moveDirection: var moveDirection : Vector3 Aproximadamente la direcci贸n desde el centro de la c谩psula al punto que tocamos. moveLenght: var moveLength : float Indica lo lejos que el character controller ha viajado hasta golpear al collider.
Cubemap VARIABLES:
format: var format : TextureFormat El formato de los datos de los p铆xeles en la textura. S贸lo lectura.
FUNCIONES:
Cubemap: static function Cubemap (size : int, format : TextureFormat, mipmap : boolean) : Cubemap
Crea una nueva textura de cubemap. La textura puede tener tamaño en cada lado y con o sin mipmaps. SetPixel: function SetPixel (face : CubemapFace, x : int, y : int, color : Color) : void Coloca el color del pixel en las coordenadas (face, x, y) GetPixel: function GetPixel (face : CubemapFace, x : int, y : int) : Color Devuelve el color del pixel en las coordenadas (face, x, y) GetPixels: function GetPixels (face : CubemapFace, miplevel : int = 0) : Color[] Devuelve el color del pixel de una cara del cubemap. Devuelve un array de colores de píxeles de una cara del cubemap. SetPixels: function SetPixels (colors : Color[], face : CubemapFace, miplevel : int = 0) : void Establece colores de pixeles de una cara del cubemap. Esta función toma un array de color y cambia los colores de los píxeles de la cara completa del cubemap. Apply: function Apply (updateMipmaps : boolean = true) : void Aplica en realidad todos los previos cambios de SetPixel y Setpixels.
Debug VARIABLES DE CLASE:
isDebugBuild: static var isDebugBuild : boolean En el diálogo Build Settings, que podemos encontrar en el menú=>File, hay un check box llamado “Development Build”. Si dicho check box está marcado, entonces isDebugBuild será true.
FUNCIONES DE CLASE:
DrawLine: static function DrawLine (start : Vector3, end : Vector3, color : Color = Color.white, duration : float = 0.0f) : void
Dibuja una línea desde el punto que le pasemos como parámetro start hasta el establecido como end con el color que le establezcamos como tercer parámetro y durante un tiempo fijado en duration. DrawRay: static function DrawRay (start : Vector3, dir : Vector3, color : Color = Color.white, duration : float = 0.0f) : void Dibuja una línea desde start a start+dir con el color que especifiquemos por una duración de tiempo también establecida. Break: static function Break () : void Pausa el editor. Log: static function Log (message : object) : void static function Log (message : object) : void LogError: static function LogError (message : object) : void Una variable de Debug.Log que anota un mensaje de error en la consola (en color rojo) LogWarning: static function LogWarning (message : object) : void
Event VARIABLES:
type: var type : EventType Variable que indica el tipo de evento. mousePosition: var mousePosition : Vector2 La posición del ratón. delta: var delta : Vector2 El movimiento relativo del ratón comparado con el último evento. button: var button : int Qué botón del ratón ha sido presionado. modifiers: var modifiers : EventModifiers Qué tecla modificadora está siendo pulsada( ahift, ctrl, alt...) clickCount: var clickCount : int
Cuántos clicks de ratón consecutivos hemos recibido. character: var character : char El tipo de caracter. commandName: var commandName : String El nombre de un evento de tipo ExecuteCommand o Validate Command ("Copy", "Cut", "Paste", "Delete", "FrameSelected", "Duplicate", "SelectAll", etc) keyCode: var keyCode : KeyCode El key code para eventos de teclado. shift: var shift : boolean ¿Está shift pulsado? (sólo lectura) control: var control : boolean ¿Está control pulsado? (sólo lectura) alt: var alt : boolean ¿Está alt pulsado? (Sólo lectura) capsLock: var capsLock : boolean ¿Está el bloqueo de mayúsculas pulsado? (sólo lectura) numeric: var numeric : boolean ¿Se está presionando alguna tecla del teclado numérico) (sólo lectura) functionKey: var functionKey : boolean ¿Es la tecla presionada una tecla de función (alt, ctrl, shift, etc)? (Sólo lectura) isKey: var isKey : boolean ¿Es este evento un evento de teclado? (sólo lectura) isMouse: var isMouse : boolean ¿Es este evento un evento de ratón? (sólo lectura)
FUNCIONES:
GetTypeForControl: function GetTypeForControl (controlID : int) : EventType Esta función devuelve un tipo de evento que es filtrado para un determinado
control cuya id pasamos como parámetro. Use: function Use () : void Evento ya utilizado. deberíamos llamar a este método cuando ya hemos usado un evento.
VARIABLES DE CLASE:
current: static var current : Event El evento actual/corriente que está siendo procesado en este mismo momento.
GameObject VARIABLES:
isStatic: var isStatic : boolean Variable sólo utilizable vía interface que especifica si un objeto es estático. transform: var transform : Transform El transform del gameobject, si lo tiene. Null si no tiene ningún transform vinculado. rigidbody: var rigidbody : Rigidbody El rigidbody vinculado a nuestro gameobject, o null si éste no tiene rigidbody. camera: var camera : Camera La cámara vinculada a nuestro gameobject, o null si éste no tiene una cámara. light: var light : Light La luz vinculada al gameobject. Null si no tiene. animation: var animation : Animation La animación vinculada al gameobject. Null si no tiene. constantForce: var constantForce : ConstantForce La constantForce vinculada a este gameobject. Null si no tiene ninguna vinculada. renderer: var renderer : Renderer El renderer vinculado al gameobject. Null si no tiene. audio: var audio : AudioSource
El audiosource vinculado al gameobject. Null si no tiene. guiText: var guiText : GUIText El guiText vinculado al gameobject. Null si no existe. networkView: var networkView : NetworkView El networkView vinculado al gameobject. Null si no existe. guiTexture: var guiTexture : GUITexture El guiTEXture vinculado al gameobject. Null si carece de él. collider: var collider : Collider El collider vinculado al gameobject, si lo tiene. Null en caso contrario. hingeJoint: var hingeJoint : HingeJoint El hingeJoint vinculado al gameobject, o null. particleEmitter: var particleEmitter : ParticleEmitter El particleEmitter vinculado al gameobject, null si no tiene. layer: var layer : int Es una variable de tipo int comprendida entre el rango 0 y 31 que indica/coloca la capa en la que el gameobject se encuentra. active: var active : boolean ¿Está el gameobject activo?. Podemos habilitarlo/deshabilitarlo cambiando a true o false este booleano. tag: var tag : String El tag (etiqueta) de este game object.
FUNCIONES:
GameObject: static function GameObject (name : String) : GameObject Nos permite crear un nuevo gameobject y pasarle un parámetro que constituirá el nombre de dicho gameobject. GetComponent: function GetComponent (type : Type) : Component Esta función devuelve el componente de tipo Type que tenga el gameobject. En caso de que el gameobject que lanza el mensaje no tenga ningún componente de ese tipo,
devuelve null. GetComponentInChildren: function GetComponentInChildren (type : Type) : Component Devuelve un componente activo de tipo que le pasamos como parámetro que pueda tener o bien el Gameobject que lanza el mensaje o bien sus hijos. GetComponents: function GetComponents (type : Type) : Component[] Esta función devuelve todos los componentes del tipo Type y los devuelve en forma de array del tipo de componente solicitado. GetComponentsInChildren: function GetComponentsInChildren (type : Type, includeInactive : boolean = false) : Component[] Devuelve todos los componentes del tipo pasado como primer parámetro que tenga ese gameobjects y sus hijos. La peculiaridad es que la función incluye un segundo parámetro opcional de tipo booleano que si establecemos en true nos permite recuperar también los componentes inactivos de ese tipo. SetActiveRecursively: function SetActiveRecursively (state : boolean) : void Función que activa o desactiva el estado del gameobject que la invoca y de todos sus hijos, en función de si el bolean que tiene como parámetro está en true o false. CompareTag: function CompareTag (tag : String) : boolean Esta función nos permite averiguar si un determinado gameobjet tiene como tag el string colocado como parámetro de la función. Si lo tiene, la función devuelve true. SendMessageUpwards: function SendMessageUpwards (methodName : String, value : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void Esta función llama a una función cuyo nombre pasamos en el parámetro methodName y que se encuentre en cualquier script que tengamos vinculado al gameobject que hace la llamada o en los "padres" de dicho gameobject.
SendMessage: function SendMessage (methodName : String, value : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void Igual que la función anterior, con la diferencia de que en esta sólo podemos llamar a funciones que estén en scripts vinculados al gameobject, pero no en sus ancestros. BroadcastMessage: function BroadcastMessage (methodName : String, parameter : object = null, options : SendMessageOptions = SendMessageOptions.RequireReceiver) : void Llama a un método con nombre methodName que esté en cualquier script vinculado a ese game object o a cualquiera de sus hijos, a diferencia de las funciones anteriores. AddComponent: function AddComponent (className : String) : Component Añade un componente a nuestro gameobject. Ese componente ha de ser una instancia de la clase que ponemos en el parámetro className, o bien puede ser el nombre de un script.
FUNCIONES DE CLASE:
CreatePrimitive: static function CreatePrimitive (type : PrimitiveType) : GameObject Crea un gameobject con una malla de tipo primitivo y el apropiado collider. FindWithTag: static function FindWithTag (tag : String) : GameObject Devuelve un gameobject con el tag que le pasamos a la función como string en su único parámetro. Si no existe ninguno. Devuelve null. FindGameObjectWithTag: static function FindGameObjectsWithTag (tag : String) : GameObject[] Esta función de clase devuelve una lista de gameobjects activos que tengan el tag requerido. Find: static function Find (name : String) : GameObject Busca un gameobject por nombre y lo devuelve.
Gizmos VARIABLES DE CLASE:
color: static var color : Color Establece el color para los gizmos que serán dibujados a continuación.
FUNCIONES DE CLASE:
DrawRay: static function DrawRay (r : Ray) : void Dibuja un rayo que empieza desde from hasta from + direction. DrawWireSphere: static function DrawWireSphere (center : Vector3, radius : float) : void Dibuja una esfera de alambre con centro y radio. DrawSphere: static function DrawSphere (center : Vector3, radius : float) : void Dibuja una esfera sólida con centro y radio. DrawWireCube: static function DrawWireCube (center : Vector3, size : Vector3) : void Dibuja una caja de alambre con centro y tamaño. DrawCube: static function DrawCube (center : Vector3, size : Vector3) : void Dibuja una caja sólida con centro y tamaño. DrawIcon: static function DrawIcon (center : Vector3, name : String) : void Dibuja un icono en posición global en la vista de escena. DrawGUITexture: static function DrawGUITexture (screenRect : Rect, texture : Texture, mat : Material = null) : void static function DrawGUITexture (screenRect : Rect, texture : Texture, mat : Material = null) : void
GUI VARIABLES DE CLASE:
skin: static var skin : GUISkin
La skin (que podemos traducir por "piel" o "aspecto") en uso. Cambiando esta variable podemos cambiar el look de nuestra GUI. color: static var color : Color Color del tintero global de la GUI. Afectará tanto a la parte trasera de los elementos (background) como a los colores del texto que escribamos sobre cualquier superficie. backgroundColor: static var backgroundColor : Color Color del tintero para todas las partes traseras (background) de los elementos renderizados para la GUI. contentColor: static var contentColor : Color Color de tinta para todo el texto renderizado en la GUI. changed: static var changed : boolean Devuelve true si algún control cambia el valor de los datos de entrada de la GUI. enabled: static var enabled : boolean Habilita/deshabilita la GUI.Si establecemos esta variable en false se deshabilitan todas las interacciones de la GUI. tooltip: static var tooltip : String Un tooltip es ese pequeña nota emergente que aparece a veces con determinada información cuando colocamos un ratón sobre un control, o dicho control tiene el foco del teclado. depth: static var depth : int El orden de profundidad que tendrá cada actividad GUI en ejecución.
FUNCIONES DE CLASE:
Label: static function Label (position : Rect, text : String) : void Crea una etiqueta (label) de texto o de imagen en la pantalla. DrawTexture: static function DrawTexture (position : Rect, image : Texture, scaleMode : ScaleMode = ScaleMode.StretchToFill, alphaBlend : boolean = true, imageAspect : float = 0) : void
Dibuja una textura dentro de un rectángulo. Box: static function Box (position : Rect, text : String) : void Como su nombre indica, crea un cuadro o caja. Button: static function Button (position : Rect, text : String) : boolean Función que crea un botón que, al ser pulsado por un usuario, dispara algún tipo de evento. RepeatButton: static function RepeatButton (position : Rect, text : String) : boolean Función que crea un botón que está activo mientras el usuario lo presiona. TextField: static function TextField (position : Rect, text : String) : String Crea un campo de texto de una línea donde el usuario puede editar un string. Devuelve un string con el texto editado. PasswordField: static function PasswordField (position : Rect, password : String, maskChar : char) : String Crea un campo de texto donde el usuario puede introducir una contraseña. Devuelve un string con la contraseña editada. TextArea: static function TextArea (position : Rect, text : String) : String Crea un área de texto de varias líneas donde el usuario puede editar un string. Devuelve el string editado. SetNextControlName: static function SetNextControlName (name : String) : void Función que establece el nombre del siguiente control. Esto hace que el siguiente control sea registrado con un nombre dado. GetNameOfFocusedControl: static function GetNameOfFocusedControl () : String Obtiene el nombre del control que tiene el foco.
FocusControl: static function FocusControl (name : String) : void Mueve el foco del teclado a un control nombrado. Toggle: static function Toggle (position : Rect, value : boolean, text : String) : boolean Crea un botón de tipo interruptor (on/off). Devuelve el nuevo valor del botón(true/false). Toolbar: static function Toolbar (position : Rect, selected : int, texts : string[]) : int Función que crea una barra de herramientas. Devuelve -un int- el índice de la toolbar seleccionado. SelectionGrid: static function SelectionGrid (position : Rect, selected : int, texts : string[], xCount : int) : int Hace una cuadrícula (grid) de botones. Devuelve un int con el índice del botón seleccionado. HorizontalSlider: static function HorizontalSlider (position : Rect, value : float, leftValue : float, rightValue : float) : float Crea una barra de desplazamiento horizontal que el usuario puede arrastrar para cambiar un valor entre un mínimo y un máximo. VerticalSlider: static function VerticalSlider (position : Rect, value : float, topValue : float, bottomValue : float) : float Crea una barra de deslizamiento vertical que el usuario puede arrastrar para cambiar un valor entre un mínimo y un máximo. HorizontalScrollbar: static function HorizontalScrollbar (position : Rect, value : float, size : float, leftValue : float, rightValue : float) : float Crea una barra de desplazamiento (scrollbar) horizontal. VerticalScrollbar: static function VerticalScrollbar (position : Rect, value : float, size : float, topValue : float, bottomValue : float, style : GUIStyle) : float
Crea una barra de desplazamiento (scrollbar) vertical. BeginGroup: static function BeginGroup (position : Rect) : void Comienza un grupo. Esta funci贸n debe emparejarse con una llamada a EndGroup. EndGroup: static function EndGroup () : void Finaliza un grupo. BeginScrollView: static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect) : Vector2 Inicia una vista de desplazamiento (scrollview) dentro de la GUI. EndScrollView: static function EndScrollView () : void Acaba un scrollview iniciado con una llamada a BeginScrollView. ScrollTo: static function ScrollTo (position : Rect) : void Desplaza todos los scrollviews incluidos para tratar de hacer visible una determinada posici贸n. Window: static function Window (id : int, clientRect : Rect, func : WindowFunction, text : String) : Rect Crea una ventana emergente y devuelve el rect谩ngulo en el que dicha ventana se ubica. DragWindow: static function DragWindow (position : Rect) : void Crea una ventana que puede arrastrarse. BringWindowToFront: static function BringWindowToFront (windowID : int) : void Trae una ventana determinada al frente del resto de ventanas flotantes. BringWindowToBack: static function BringWindowToBack (windowID : int) : void Coloca una ventana determinada al fondo de las ventanas flotantes. FocusWindow: static function FocusWindow (windowID : int) : void Hace que una ventana se convierta en la ventana activa. UnFocusWindow: static function UnfocusWindow () : void Quita el foco de todas las ventanas.
GUIElement FUNCIONES:
HitTest: function HitTest (screenPosition : Vector3, camera : Camera = null) : boolean Es un punto en la pantalla dentro del elemento. Devuelve true si la posición pasada como parámetro screenPosition está contenida en este GUIElement. GetScreenRect: function GetScreenRect (camera : Camera = null) : Rect Devuelve lo bordes del rectángulo del GUIElement en coordenadas de pantalla.
GUILayout FUNCIONES DE CLASE:
Label: static function Label (image : Texture, params options : GUILayoutOption[]) : void Hace una etiqueta de distribución automática. Box: static function Box (image : Texture, params options : GUILayoutOption[]) : void crea una caja de distribución automática. Button: static function Button (image : Texture, params options : GUILayoutOption[]) : boolean Crea un botón con distribución automática que devuelve true cuando el usuario lo presiona. RepeatButton: static function RepeatButton (image : Texture, params options : GUILayoutOption[]) : boolean Crea un botón que devuelve true tanto tiempo como el usuario lo mantiene pulsado. TextField: static function TextField (text : String, params options : GUILayoutOption[]) : String Crea un campo de texto de una linea donde el usuario puede editar un string. PasswordField: static function PasswordField (password : String,
maskChar : char, params options : GUILayoutOption[]) : String Crea un campo de texto donde el usuario puede entrar una contraseña. Devuelve la contraseña editada. TextArea: static function TextArea (text : String, params options : GUILayoutOption[]) : String Crea un campo de texto multilínea donde el user puede editar un string, y devuelve dicho string. Toggle: static function Toggle (value : boolean, image : Texture, params options : GUILayoutOption[]) : boolean Crea un botón que alterna on/off. Devuelve un booleano que indica el estado de dicho botón. Toolbar: static function Toolbar (selected : int, images : Texture[], params options : GUILayoutOption[]) : int Crea un toolbar (barra de herramientas). Devuelve un int que contiene el índice del botón seleccionado. SelectionGrid: static function SelectionGrid (selected : int, texts : string[], xCount : int, params options : GUILayoutOption[]) : int Crea una cuadrícula de selección, devolviendo el int con el índice del botón seleccionado. HorizontalSlider: tatic function HorizontalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float Crea una barra de desplazamiento horizontal que el usuario puede arrastrar desde un mínimo hasta un máximo. Devuelve un float con el valor que ha sido escogido por el usuario. VerticalSlider: static function VerticalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float Crea una barra de desplazamiento vertical que el usuario puede arrastrar desde un mínimo hasta un máximo. HorizontalScrollbar: static function HorizontalScrollbar (value : float,
size : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float Crea un scrollbar horizontal. Devuelve el valor modificado en float, que puede ser cambiado por el usuario arrastrando el scrollbar o haciendo click en las flechas al final. VerticalScrollbar: static function VerticalScrollbar (value : float, size : float, topValue : float, bottomValue : float, params options : GUILayoutOption[]) : float Crea un scrollbar vertical. Space: static function Space (pixels : float) : void Inserta un espacio en el actual grupo de distribución. La dirección de dicho espacio dependerá del la dirección del grupo de distribución en el que estemos trabajando. FlexibleSpace: static function FlexibleSpace () : void Inserta un elemento de espacio flexible. Esta función utiliza cualquier espacio que sobra en un diseño. BeginHorizontal: static function BeginHorizontal (params options : GUILayoutOption[]) : void Empieza un grupo de controles horizontal. Todos los controles dentro de este elemento serán colocados horizontalmente uno junto a otro. El grupo debe cerrarse con una llamada a EndHorizontal. EndHorizontal: static function EndHorizontal () : void Cierra el grupo abierto por BeginHorizontal. BeginVertical: static function BeginVertical (params options : GUILayoutOption[]) : void Empieza un grupo de controles vertical. Todos los controles dentro de este elemento serán situados verticalmente uno debajo del otro. El grupo debe cerrarse con EndVertical. EndVertical: static function EndVertical () : void
Cierra un grupo iniciado por BeginVertical. BeginArea: static function BeginArea (screenRect : Rect) : void Comienza un bloque GUILayout de controles GUI en un determinado área de la pantalla. EndArea: static function EndArea () : void Cierra un área de controles abierto con BeginArea. BeginScrollView: static function BeginScrollView (scrollPosition : Vector2, params options : GUILayoutOption[]) : Vector2 Comienza un scrollview (o vista de desplazamiento) automáticamente distribuído. EndScrollView: static function EndScrollView () : void Finaliza un scroll view empezado con una llamada a BeginScrollView. Window: static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, text : String, : ) : Rect Crea una ventana emergente que distribuye su contenido automáticamente. Width: static function Width (width : float) : GUILayoutOption Opción pasada a un control para darle una anchura absoluta. MinWidth: static function MinWidth (minWidth : float) : GUILayoutOption Opción pasada a un control para especificar una anchura mínima. MaxWidth: static function MaxWidth (maxWidth : float) : GUILayoutOption Opción pasada a un control para especificar una anchura máxima. Height: static function Height (height : float) : GUILayoutOption Opción pasada a un control para darle una altura absoluta. MinHeight: static function MinHeight (minHeight : float) : GUILayoutOption Opción pasada a un control para especificar una altura mínima. MaxHeight: static function MaxHeight (maxHeight : float) : GUILayoutOption Opción pasada a un control para especificar una altura máxima. ExpandWidth: static function ExpandWidth (expand : boolean) :
GUILayoutOption Opción pasada a un control para habilitar o deshabilitar la expansión horizontal. ExpandHeight: static function ExpandHeight (expand : boolean) : GUILayoutOption Opción pasada a un control para habilitar o deshabilitar la expansión vertical.
GUIText VARIABLES:
text: var text : String Variable que contiene el texto que se muestra en el GUI. material: var material : Material El material usado para renderizar el texto. pixelOffset: var pixelOffset : Vector2 El desplazamiento en píxeles del texto desde su posición inicial (marcada por su transform) en base a los valores contenidos en un Vector2. font: var font : Font La fuente usada para el texto. Podemos asignar una de entre las que tengamos disponibles. alignment: var alignment : TextAlignment La alineación del texto. anchor: var anchor : TextAnchor El ancla del texto. TextAnchor es una enumeración que permite indicar dónde se colocará el ancla o fijación del texto. lineSpacing: var lineSpacing : float El multiplicador del espacio de interlineado. Esta cantidad será multiplicada por el espacio de línea definido en la fuente. tabSize: var tabSize : float El multiplicador de la anchura del tab. Esta cantidad se multiplicará con la anchura de tab definida en la fuente.
fontSize: var fontSize : int El tamaño de fuente a usar (para fuentes dinámicas). fontStyle: var fontStyle : FontStyle Lo mismo que la anterior para el estilo de Fuentes dinámicas.
GUITexture VARIABLES:
color: var color : Color El color de la textura de la GUI. texture: var texture : Texture La textura usada para el dibujo. pixelInset: var pixelInset : Rect Inserción de pixels usada para ajustar píxeles para tamaño y posición
HingeJoint VARIABLES:
motor: var motor : JointMotor La variable motor aplicará una fuerza en aumento hasta conseguir la velocidad deseada en grados por segundo. limits: var limits : JointLimits Variable que establece/indica los límites de nuestro hingejoint. spring: var spring : JointSpring Esta variable intenta alcanzar un ángulo que es su objetivo a base de añadir fuerzas de resorte y amortiguación. useMotor: var useMotor : boolean Habilita/deshabilita el motor del joint. useLimits: var useLimits : boolean Habilita/deshabilita los límites del joint.
useSpring: var useSpring : boolean Habilita/deshabilita el resorte del joint. velocity: var velocity : float Devuelve la velocidad actual del joint en grados por segundo. angle: var angle : float Devuelve el ángulo actual en grados del joint con respecto a la posición inicial. Es una variable de solo lectura.
Input VARIABLES DE CLASE:
mousePosition: static var mousePosition : Vector3 Variable de sólo lectura que indica la posición actual del ratón en coordenadas de píxeles, donde la esquina inferior izquierda de la pantalla o ventana está en (0, 0) y la superior derecha en (Screen.width, Screen.height). anyKey: static var anyKey : boolean Booleano de sólo lectura que comprueba si hay alguna tecla o botón del ratón apretada en ese momento. anyKeyDown: static var anyKeyDown : boolean Variable de sólo lectura que devuelve true el primer frame en que el usuario golpea cualquier tecla o botón del ratón. inputString: static var inputString : String Variable de sólo lectura que devuelve la entrada de teclado introducida este frame. acceleration: static var acceleration : Vector3 Ultima medicion de aceleración lineal de un dispositimo en espacio tridimensional. Sólo lectura accelerationEvents: static var accelerationEvents : AccelerationEvent[] Variable de sólo lectura que devuelve una lista de mediciones de aceleración ocurridas durante el último frame.
accelerationEventCount: static var accelerationEventCount : int Número de mediciones de aceleración ocurrida durante el último frame. eatKeyPressOnTextFieldFocus: static var eatKeyPressOnTextFieldFocus : boolean Propiedad que indica si las teclas impresas son comidas por la entrada de texto si esta tiene el foco (por defecto true).
FUNCIONES DE CLASE:
GetAxis: static function GetAxis (axisName : String) : float Devuelve el valor del eje virtual identificado por el parámetro axisName. GetAxisRaw: static function GetAxisRaw (axisName : String) : float Devuelve el valor del eje virtual identificado por el parámetro axisName sin ningún filtro de suavizado aplicado. GetButton: static function GetButton (buttonName : String) : boolean Devuelve true mientras el botón virtual que le pasemos como parámetro en formato string esté presionado. GetButtonDown: static function GetButtonDown (buttonName : String) : boolean Devuelve true durante el frame en que el jugador aprieta el botón virtual identificado como buttonName. GetButtonUp: static function GetButtonUp (buttonName : String) : boolean Devuelve true el primer frame en que el jugador libera el botón virtual identificado como buttonName. GetKey: static function GetKey (name : String) : boolean Devuelve true mientras el jugador aprieta la tecla identificada como name. GetKeyDown: static function GetKeyDown (name : String) : boolean Devuelve true durante el frame en que el usuario empieza a presionar la tecla identificada como name. GetKeyUp: static function GetKeyUp (name : String) : boolean Devuelve true durante el frame en que el jugador libera la tecla identificada por name.
GetJoystickNames: static function GetJoystickNames () : string[] Devuelve un array de strings describiendo los joysticks conectados. GetMouseButton: static function GetMouseButton (button : int) : boolean Devuelve true si el botón indicado del ratón es apretado. La variable button es un int que representa 0 para el botón izquierdo, 1 para el derecho y 2 para el central. GetMouseButtonDown: static function GetMouseButtonDown (button : int) : boolean Devuelve true durante el frame en que el usuario aprieta el botón del ratón indicado. GetMouseButtonUp: static function GetMouseButtonUp (button : int) : boolean Devuelve true durante el frame en que el usuario libera el botón del ratón indicado. ResetInputAxes: static function ResetInputAxes () : void Resetea todos los inputs, con lo que todos los axes y botones retornan a 0. GetAccelerationEvent: static function GetAccelerationEvent (index : int) : AccelerationEvent Devuelve mediciones de aceleración que ocurrieron durante el último frame.
Joint VARIABLES:
connectedBody: var connectedBody : Rigidbody Esta variable referencia a otro rigidbody con el que nuestr joint conecta. axis: var axis : Vector3 El eje altededor del cual el rigidbody está constreñido, indicado en coordenadas locales. anchor: var anchor : Vector3 La posición del ancla alrededor de la cual el movimiento de los joints está constreñido. La posición es definida en espacio local. breakForce: var breakForce : float
La fuerza que es necesario aplicarle a este joint para romperlo. La fuerza podría venir por colisiones con otros objetos, fuerzas aplicadas con rigidbody.AddForce o por otros joints. breakTorque: var breakTorque : float La torsión que es necesario aplicar a este joint para romperlo.
FUNCIONES:
OnJointBreak: function OnJointBreak (breakForce : float) : void Función que es llamada cuando un joint vinculado al mismo game object se rompe.
Light VARIABLES:
type: var type : LightType El tipo de luz. color: Var color : Color El color de la luz. intensity: var intensity : float La intensidad de la luz. shadows: var shadows : LightShadows Indica cómo proyecta sombras la luz. shadowStrenght: var shadowStrength : float Establece/indica la fuerza de las sombras. shadowBias: var shadowBias : float Vendría a ser la perpendicular de la sombra respecto del objeto que la emite. shadowSoftness: var shadowSoftness : float Suavidad de las sombras de las luces direccionales. shadowSoftnessFade: var shadowSoftnessFade : float Velocidad de fadeout de las sombras de las luces direccionales. range: var range : float
El diámetro del rango o haz de luz, en luces de tipo spot y point. spotAngle: var spotAngle : float El ángulo en grados de las luces de tipo spotlight. cookie: var cookie : Texture Variable de tipo textura que establece la textura de la galleta proyectada por la luz. flare: var flare : Flare Selecciona un halo para la luz. Este ha de ser previamente asignado en el inspector. renderMode: var renderMode : LightRenderMode Establece cómo renderizar la luz. cullingMask: var cullingMask : int Es usado para separar luces de la escena de manera selectiva.
LightmapSettings VARIABLES DE CLASE:
lightmaps: static var lightmaps : LightmapData[] Es un array de tipo LightmapData que puede almacenar diferentes lightmaps. lightmapsMode: static var lightmapsMode : LightmapsMode Modo de renderizado de lightmaps.
Material VARIABLES:
shader: var shader : Shader Es el shader del material. En modelado 3d, un shader vendría a ser el algoritmo que indica cómo una superficie ha de responder ante la luz. color: var color : Color El color principal del material.
mainTexture: var mainTexture : Texture La textura principal del material. mainTextureOffset: var mainTextureOffset : Vector2 Nos permite desplazar la textura principal. mainTextureScale: var mainTextureScale : Vector2 La escala de la textura principal.
FUNCIONES:
Material: static function Material (contents : String) : Material Crea un material temporal a partir de un shader descrito por un string. SetColor: function SetColor (propertyName : String, color : Color) : void Nos permite indicar el nombre para un determinado color. GetColor: function GetColor (propertyName : String) : Color Obtiene el valor de un color con nombre. SetTexture: function SetTexture (propertyName : String, texture : Texture) : void Establece el nombre de una textura. GetTexture: function GetTexture (propertyName : String) : Texture Obtiene el nombre de una textura. SetTextureOffset: function SetTextureOffset (propertyName : String, offset : Vector2) : void Establece el lugar de desplazamiento de la textura pasada como primer parรกmetro. GetTextureOffset function GetTextureOffset (propertyName : String) : Vector2 Obtiene la ubicaciรณn del desplazamiento de la textura pasada como parรกmetro. SetTextureScale: function SetTextureScale (propertyName : String, scale : Vector2) : void Establece la escala de la textura pasada como primer parรกmetro.
GetTextureScale: function GetTextureScale (propertyName : String) : Vector2 Obtiene la escala de la textura pasada como par谩metro. SetFloat: function SetFloat (propertyName : String, value : float) : void Establece un valor tipo float con nombre. GetFloat: function GetFloat (propertyName : String) : float Obtiene un valor de tipo float con nombre. HasProperty: function HasProperty (propertyName : String) : boolean Comprueba si el shader del material tiene una propiedad con un nombre determinado. GetTag: function GetTag (tag : String, searchFallbacks : boolean, defaultValue : String = "") : String Obtiene el valor del tag del shader del material. Lerp: function Lerp (start : Material, end : Material, t : float) : void Interpola propiedades entre dos materiales. Hace que todos los colores y valores del primer material sean convertidos en los valores del segundo material en el tiempo t. CopyPropertiesFromMaterial: function CopyPropertiesFromMaterial (mat : Material) : void Copia propiedades de otro material en este material.
Mathf VARIABLES DE CLASE:
PI: static var PI : float El famoso 3.141592. (s贸lo lectura) Infinity: static var Infinity : float Representaci贸n del infinito positivo (s贸lo lectura) NegativeInfinity: static var NegativeInfinity : float
Una representación del infinito negativo (sólo lectura) Deg2Rad: static var Deg2Rad : float Conversión constante de grados a radianes (sólo lectura) Rad2Deg: static var Rad2Deg : float Conversión constante de radianes a grados (sólo lectura) Epsilon: static var Epsilon : float
FUNCIONES DE CLASE:
Sin: static function Sin (f : float) : float Devuelve el seno del ángulo f en radianes. Cos: static function Cos (f : float) : float Devuelve el coseno del ángulo f en radianes. Tan: static function Tan(f : float) : float Devuelve la tangente del ángulo f en radianes. Asin: static function Asin (f : float) : float Devuelve el arco seno de f menos el ángulo en radianes cuyo seno es f. Acos: static function Acos (f : float) : float Devuelve el arco coseno de f menos el ángulo en radianes cuyo coseno es f. Atan: static function Atan (f : float) : float Devuelve el arco tangente de f menos el ángulo en radianes cuya tangente es f. Atan2: static function Atan2 (y : float, x : float) : float Devuelve el ángulo en radianes cuya tangente es y/x. Sqrt: static function Sqrt (f : float) : float Devuelve la raíz cuadrada de f. Abs: static function Abs (value : float) : float Devuelve el valor absoluto de value. Min: static function Min (a : float, b : float) : float Devuelve el valor mínimo de dos o más valores dados. Max: static function Max (a : float, b : float) : float
Devuelve el valor máximo de dos o más valores. Pow: static function Pow (f : float, p : float) : float Devuelve f elevado a la potencia p. Exp: static function Exp (power : float) : float Devuelve la potencia natural de un determinado número. Log: static function Log (f : float, p : float) : float Devuelve el logaritmo de un determinado número en una base especificada. Log10: static function Log10 (f : float) : float Devuelve el logaritmo en base diez de un determinado número. Ceil: static function Ceil (f : float) : float Devuelve el integer más pequeño igual o mayor que f. Floor: static function Floor (f : float) : float Devuelve el mayor integer igual o más pequeño que f. Round: static function Round (f : float) : float Devuelve f redondeado al integer más cercano. CeilToInt: static function CeilToInt (f : float) : int Devuelve el integer más pequeño igual o mayor que f. FloorToInt: static function FloorToInt (f : float) : int Devuelve el mayor integer menor o igual que f. RoundToInt: static function RoundToInt (f : float) : int Devuelve f rendondeada al integer más cercano. Sign: static function Sign (f : float) : float Devuelve el signo de f. Devuelve 1 si es positivo o cero, y -1 si f es negativo. Clamp: static function Clamp (value : float, min : float, max : float) : float Restringe un valor entre un mínimo y un máximo, sean floats o ints. Clamp01: static function Clamp01 (value : float) : float Fija un valor entre 0 y 1 y lo devuelve. Lerp: static function Lerp (from : float, to : float, t : float) : float Interpola a hacia b pasando por t. t queda fijada entre 0 y 1. Cuando t = 0
devuelve from. Cuando t = 1 devuelve to. Cuando t = 0.5 devuelve la media de a y b. LerpAngle: static function LerpAngle (a : float, b : float, t : float) : float Lo mismo que lerp, pero asegurándonos de interpolar valores correctamente cuando dé un giro de 360 grados. MoveTowards: static function MoveTowards (current : float, target : float, maxDelta : float) : float Mueve el valor que indicamos en el parámetro current hacia el que indicamos en target. MoveTowardsAngle: static function MoveTowardsAngle (current : float, target : float, maxDelta : float) : float Lo mismo que MoveTowards pero estando seguros de que los valores se interpolarán correctamente cuando gire 360 grados. SmoothStep: static function SmoothStep (from : float, to : float, t : float) : float Interpola entre mínimo y máximo y facilita entrada y salida de los límites. Approximately: static function Approximately (a : float, b : float) : boolean Compara si dos valores en punto flotante son similares. SmoothDamp: static function SmoothDamp (current : float, target : float, ref currentVelocity : float, smoothTime : float, maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float Gradualmente cambia un valor hacia un objetivo en un determinado tiempo. SmoothDampAngle: static function SmoothDampAngle (current : float, target : float, ref currentVelocity : float, smoothTime : float, maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float Cambia gradualmente un ángulo dado en grados hacia el ángulo que constituye el objetivo en un tiempo determinado. Repeat: static function Repeat (t : float, length : float) : float Introduce en un bucle el valor t, de tal manera que nunca sea más grande que
length y nunca más pequeño que 0. PingPong: static function PingPong (t : float, length : float) : float Hace rebotar el valor t, de tal manera que nunca sea mayor que length y nunca mayor que 0. El valor retornado se moverá atrás y adelante entre 0 y length. InverseLerp: static function InverseLerp (from : float, to : float, value : float) : float Calcula el parámetro Lerp entre dos valores. ClosestPowerOfTwo: static function ClosestPowerOfTwo (value : int) : int Retorna la potencia de dos más cercana. IsPowerOfTwo: static function IsPowerOfTwo (value : int) : boolean Devuelve true si el valor es potencia de dos NextPowerOfTwo: static function NextPowerOfTwo (value : int) : int Devuelve el valor de la siguiente potencia de dos. DeltaAngle: static function DeltaAngle (current : float, target : float) : float Calcula la diferencia más corta entre dos ángulos dados.
Mesh FUNCIONES:
vertices: var vertices : Vector3[] Devuelve una copia de la posición del vértice o asigna un nuevo array de posiciones de vértices. normals: var normals : Vector3[] Los normales de la malla. Si la malla no contiene normales un array vacío es retornado. tangents: var tangents : Vector4[] Las tangentes de la malla. uv: var uv : Vector2[] Las coordenadas de la textura base de la malla. uv2: var uv2 : Vector2[]
El segundo conjunto de coordenadadas de textura de la malla, si lo hubiere. bounds: var bounds : Bounds El volumen de bordes de la malla. colors: var colors : Color[] Devuelve los colores del vértice de la malla. Si no hay colores devuelve un array vacío. triangles: var triangles : int[] Un array conteniendo todos los triángulos de la malla. vertexCount: var vertexCount : int Variable de sólo lectura que devuelve el número de vértices en la malla. subMeshCount: var subMeshCount : int El número de submallas. Cada material tiene una lista de triángulos separada. boneWeights: var boneWeights : BoneWeight[] La pintura de pesos de cada vértice.
FUNCIONES:
Mesh: static function Mesh () : Mesh Crea una malla vacía. Clear: function Clear () : void Limpia todos los datos de los vértices y todos los índices de los triángulos. RecalculateBounds: function RecalculateBounds () : void Recalcula el volumen de bordes de la malla desde los vértices. RecalculateNormals: function RecalculateNormals () : void Recalcula los normales de la malla desde los triángulos y vértices. Optimize: function Optimize () : void Optimiza la malla para mostrarla. GetTriangles: function GetTriangles (submesh : int) : int[] Devuelve la lista de triángulos de la submalla. SetTriangles: function SetTriangles (triangles : int[], submesh : int) :
void Establece la lista de triángulos para la submalla. CombineMeshes: function CombineMeshes (combine : CombineInstance[], mergeSubMeshes : boolean = true, useMatrices : boolean = true) : void Combina varias mallas dentro de la malla.
MeshCollider VARIABLES:
sharedMesh: var sharedMesh : Mesh Hace referencia al objeto malla que nuestro gameobject está usando como detector de colisiones, si lo hay. convex: var convex : boolean Con esta variable establecida en true nuestro collider de malla automáticamente pasa a ser convexo y todas las entradas y agujeros que pudiera tener nuestra malla desaparecen, permitiendo una detección de colisiones mucho más completa. smoothSphereCollisions: var smoothSphereCollisions : boolean Usa normales interpolados para colisiones de esferas en lugar de normales planos poligonales. Esto suaviza los baches para rodamientos de esferas sobre superficies suaves.
MeshFilter VARIABLES:
mesh: var mesh : Mesh Devuelve la instancia de malla asignada al mesh filter. Si no se ha asignado ninguna malla al mesh filter se creará y asignará una nueva. sharedMesh: var sharedMesh : Mesh Devuelve la malla compartida del mesh filter.
MonoBehaviour
FUNCIONES:
Invoke: function Invoke (methodName : String, time : float) : void Llama a la función que le pasemos como primer parámetro en el tiempo contado en segundos que le indiquemos como segundo parámetro. InvokeRepeating: function InvokeRepeating (methodName : String, time : float, repeatRate : float) : void Invoca la función que le pasamos como string para dentro del número de segundos que le pasamos como segundo parámetro, y una vez se ejecuta, lo vuelve a hacer cada repeatRate segundos (el tercer parámetro). CancelInvoke: function CancelInvoke () : void CancelInvoke cancela las funciones que hayan sido invocadas por las dos funciones anteriores para un determinado script. IsInvoking: function IsInvoking (methodName : String) : boolean Devuelve true si hay algún invoke pendiente de la función que se introduce como parámetro. StartCoroutine: function StartCoroutine (routine : IEnumerator) : Coroutine Inicia una corrutina cuya ejecución puede ser pausada en cualquier momento usando una instrucción yield. StopCoroutine: function StopCoroutine (methodName : String) : void Detiene las corrutinas con el nombre que le suministramos como parámetro iniciadas en este script. StopAllCoroutines: function StopAllCoroutines () : void Detiene todas las corrutinas que estén corriendo en ese script. Update: function Update () : void Esta función es llamada cada frame, si el Monobehaviour (script que la invoca) está activo.
LateUpdate: function LateUpdate () : void LateUpdate es llamado una vez todas las funciones Update han sido llamadas. FixedUpdate: function FixedUpdate () : void Esta función se ejecuta cada cierto número preestablecido y fijo de frames. Awake: function Awake () : void Awake es llamada cuando se inicia el script, osea, cuando se carga la escena, y justo después de que se carguen los objects (gameobjects y components) a que el script hace referencia. Start: function Start () : void Es llamada después de Awake y antes de Update. Al igual que awake, sólo es llamada una vez a lo largo de toda la vida del script. Reset: function Reset () : void Resetea a los valores por defecto, restaurando los valores originales. Esta función es llamada cuando se pulsa reset en el inspector o cuando se añade un componente por primera vez. OnMouseEnter: function OnMouseEnter () : void Esta función es llamada cuando el cursor entra en un collider o un GUIElement. OnMouseOver: function OnMouseOver () : void Esta función es llamada cada frame en la que el mouse permanezca sobre el Collider o GUIElement, a diferencia de onMouseEnter, que sólo se dispara cuando entra el mouse. OnMouseExit: function OnMouseExit () : void Esta función es llamada cuando el ratón ya no esta sobre un GUIElement o un Collider. OnMouseDown: function OnMouseDown () : void Es llamada cuando el usuario pulsa el botón del mouse sobre un GUIElement o Collider. OnMouseUp: function OnMouseUp () : void Esta función es llamada cuando el usuario libera/suelta el botón del ratón. Es
llamada incluso si el mouse no está al soltar el botón sobre el mismo GUIElement o Collider en que estaba cuando el botón fue presionado. OnMouseUpAsButton: function OnMouseUpAsButton () : void Es llamada sólo cuando el mouse es liberado estando sobre el mismo GUIElement o Collider en el que fue presionado. OnMouseDrag: function OnMouseDrag () : void Es llamada esta función cuando el usuario presiona el botón del mouse sobre un GUIElement o un Collider y todavía lo mantiene presionado. Es llamada cada frame mientras el botón siga presionado. OnControllerColliderHit: function OnControllerColliderHit (hit : ControllerColliderHit) : void Es llamada cuando nuestro character controller golpea un collider mientras realiza un movimiento. OnParticleCollision: unction OnParticleCollision (other : GameObject) : void Esta función es llamada cuando una partícula choca con un collider. OnBecameVisible: function OnBecameVisible () : void Se llama a esta función cuando el renderer se convierte en visible por alguna cámara. OnBecameInvisible: function OnBecameInvisible () : void Es llamada cuando el renderer ya no es visible por ninguna cámara. OnLevelWasLoaded: function OnLevelWasLoaded (level : int) : void Esta función es llamada después de que un nuevo level ha sido cargado. OnEnable: function OnEnable () : void Es llamada cuando el objeto pasa a estar habilitado y activo. OnDisable: function OnDisable () : void Es llamada cuando el objeto se convierte en deshabilitado o inactivo. OnDestroy: function OnDestroy () : void Es llamada cuando el MonoBehaviour (script) es destruido. Sólo puede ser llamada para gameobjects que previamente hayan estado activos.
OnPreCull: function OnPreCull () : void Es llamada antes de que la cámara deseche la escena. OnPreRender: function OnPreRender () : void Es llamada antes de que la cámara empiece a renderizar la escena. Sólo se llama si el script está vinculado a la cámara y activo. OnPostRender: function OnPostRender () : void Es llamada después de que la cámara acaba de renderizar la escena, siempre que el script esté vinculado con la cámara y activo. OnRenderObject: function OnRenderObject () : void Es llamada después de que la cámara renderiza la escena. OnWillRenderObject: function OnWillRenderObject () : void Esta función es llamada una vez por cada cámara si el objeto es visible. OnGUI: function OnGUI () : void Es llamada para renderizar y manejar eventos GUI. OnRenderImage: function OnRenderImage (source : RenderTexture, destination : RenderTexture) : void Es llamada cuando se tiene toda la información de renderizado de una imagen, permitiendo modificar la imagen final procesándola con filtros. OnDrawGizmosSelected: function OnDrawGizmosSelected () : void Implementa esta function si quieres dibujar gizmos sólo si el objeto está seleccionado. OnDrawGizmos: function OnDrawGizmos () : void Implementa esta función si quieres dibujar gizmos que aparezcan siempre dibujados. OnApplicationPause: function OnApplicationPause (pause : boolean) : void Es enviada a todos los gameobjects cuando el jugador presiona la pausa. OnApplicationFocus: function OnApplicationFocus (focus : boolean) : void Función enviada a todos los gameobjects cuando el jugador obtiene o pierde el foco.
OnApplicationQuit: function OnApplicationQuit () : void Función que se envía a todos los gameobjects antes de que la aplicación se cierre. En el editor es llamada cuando el usuario detiene el play, en la web es llamada cuando la página se cierra. OnPlayerConnected: unction OnPlayerConnected (player : NetworkPlayer) : void Es llamada en el servidor cada vez que un nuevo jugador se conecta con éxito. OnServerInitialized: function OnServerInitialized () : void Llamada en el server cada vez que Network.InitializeServer es invocada y completada. OnConnectedToServer: function OnConnectedToServer () : void Es llamada esta función en el cliente cuando consigues conectarte con éxito al servidor. OnPlayerDisconnected: function OnPlayerDisconnected (player : NetworkPlayer) : void Llamada en el server cada vez que un jugador se desconecta del server. OnDisconnectedFromServer: function OnDisconnectedFromServer (mode : NetworkDisconnection) : void Llamada en el cliente cuando la conexión se pierde o desconectas del servidor. OnFailedToConnect: function OnFailedToConnect (error : NetworkConnectionError) : void Llamada en el cliente cuando un intento de conexión falla por alguna razón. La razón por la que falla es pasada como una enumeración de tipo Network.ConnectionError. OnFailedToConnectToMasterServer: unction OnFailedToConnectToMasterServer (error : NetworkConnectionError) : void Llamada en clientes o servidores cuando hay un problema conectando con el MasterServer. La razón del error es pasada como una enumeración de tipo Network.ConnectionError.
OnMasterServerEvent: function OnMasterServerEvent (msEvent : MasterServerEvent) : void Llamada en clientes o servidores cuando informan de eventos desde el MasterServer, como por ejemplo que haya tenido éxito el registro en el host. OnNetworkInstantiate: Function OnNetworkInstantiate (info : NetworkMessageInfo) : void Llamada en objetos que han sido instanciados en red con Network.Instantiate. OnSerializeNetworkView: function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo) : void Usada para personalizar la sincronización de variables en un script controlado por una network view.
Object VARIABLES:
name: var name : String El nombre del objeto. Lo comparten el objeto y sus componentes. hideFlags: var hideFlags : HideFlags Permite que un objeto desaparezca del inspector o de la jerarquía.
FUNCIONES:
GetInstanceID: function GetInstanceID () : int Unity asigna a cada objeto un identificador único. Esta función devuelve ese identificador. ToString: function ToString () : String Devuelve un string con el nombre del gameobject que hace la consulta.
FUNCIONES DE CLASE:
Operator bool: static implicit function bool (exists : Object) : boolean
¿Existe este objeto? Instantiate: static function Instantiate (original : Object, position : Vector3, rotation : Quaternion) : Object Esta función lo que hace es clonar el objeto que le pasamos como primer parámetro, y devolver ese clon del objeto original, ubicándolo en posición y rotación determinadas.
Destroy: static function Destroy (obj : Object, t : float = 0.0F) : void Esta función borra del juego un gameobject, un componente o un asset. DestroyImmediate: static function DestroyImmediate (obj : Object, allowDestroyingAssets : boolean = false) : void Esta función destruye inmediatamente un objeto. FindObjectsOfType: static function FindObjectsOfType (type : Type) : Object[] Función que devuelve un array con todos los objetos activos del tipo indicado. FindObjectOfType: static function FindObjectOfType (type : Type) : Object Esta función devuelve el primer objeto activo que Unity encuentre que sea del tipo que le pasamos como parámetro. Operator ==: static operator == (x : Object, y : Object) : boolean Compara si dos objetos son iguales. Operator !=: static operator != (x : Object, y : Object) : boolean Compara si dos objetos son diferentes. DontDestroyOnLoad: tatic function DontDestroyOnLoad (target : Object) : void Con esta función conseguimos que el objeto que colocamos como parámetro no se destruya cuando se cargue una nueva escena.
ParticleEmitter VARIABLES:
emit: var emit : boolean Booleano que indica si deben las partículas ser automáticamente emitidas cada
frame o no. minSize: var minSize : float El tamaño mínimo que cada partícula puede tener cuando se genera. maxSize: var maxSize : float El tamaño máximo que cada particular puede tener al tiempo de ser generada. minEnergy: var minEnergy : float El mínimo tiempo de vida de cada particular, medido en segundos. maxEnergy: var maxEnergy : float El máximo tiempo de vida de cada partícula, medido en segundos. minEmission: var minEmission : float El mínimo número de partículas que serán generadas cada segundo. maxEmission: El máximo número de partículas que serán generadas cada segundo. emitterVelocityScale: var emitterVelocityScale : float La cantidad de la velocidad de emisión que las partículas heredan. worldVelocity: var worldVelocity : Vector3 La velocidad inicial de las partículas en el mundo global, a lo largo de x,y,z. localVelocity: var localVelocity : Vector3 Es como la anterior, pero el desplazamiento se efectúa en las coordenadas locales del propio sistema de partículas. rndVelocity: var rndVelocity : Vector3 Una velocidad aleatoria con relación a los ejes x,y,z que es añadida a la velocidad. useWorldSpace: var useWorldSpace : boolean Si está habilitado, las partículas no se mueven cuando el emitter se mueve. Si está en false, cuando muevas el emitter, las partículas lo siguen. rndRotation: var rndRotation : boolean Si está habilitado, las partículas serán generadas con rotaciones aleatorias. angularVelocity: var angularVelocity : float
La velocidad angular de las nuevas partículas en grados por segundo. rndAngularVelocity: var rndAngularVelocity : float Un modificador aleatorio de velocidad angular para nuevas partículas. particles: var particles : Particle[] Devuelve una copia de todas las partículas y asigna un array de todas las partículas. particleCount: var particleCount : int El corriente número de partículas. Variable de sólo lectura. enabled: var enabled : boolean Establece el ParticleEmitter en on o off. Un ParticleEmitter que no está habilitado no emitirá ninguna partícula, y las partículas emitidas no se animarán. Así, este valor nos permite pausar un sistema de partículas.
FUNCIONES:
ClearParticles: function ClearParticles () : void Borra todas las partículas del sistema de partículas. Emit: function Emit () : void Emite un número de partículas. Simulate: function Simulate (deltaTime : float) : void Avanzado sistema de simulación de partículas por un tiempo dado. Es útil para precalentar un sistema de partículas.
PhysicMaterial VARIABLES:
dynamicFriction: var dynamicFriction : float La fricción usada cuando ya hay movimiento. Este valor ha de estar entre 0 y 1, siendo 0 la fricción del hielo y 1 la del caucho. staticFriction: var staticFriction : float
La fricción usada cuando un objeto está reposando en una superficie. bounciness: var bounciness : float Qué capacidad de rebote tiene la superficie. 0 es no rebote. Un valor de 1 implica rebote sin pérdida de energía. frictionDirection2: var frictionDirection2 : Vector3 Se refiere a la dirección de anisotropía. La fricción anisotrópica está habilitada salvo que su vector esté a cero. dynamicFriction: var dynamicFriction2 : float Si la fricción anisotrópica está habilitada (frictionDirection2 no vale cero), dynamicFriction2 será aplicada a lo largo del eje/s de frictionDirection2 que no valga cero. staticFriction2: var staticFriction2 : float Si la fricción anisotrópica está habilitada, staticFriction2 será aplicada a lo largo de frictionDirection2. frictionCombine: var frictionCombine : PhysicMaterialCombine Determina cómo se combina la fricción. bounceCombine: var bounceCombine : PhysicMaterialCombine Determina cómo la capacidad de rebote es combinada.
FUNCIONES:
PhysicMaterial: static function PhysicMaterial () : PhysicMaterial Función constructora que crea un nuevo material.
Physics VARIABLES DE CLASE:
gravity: static var gravity : Vector3 La gravedad aplicada a todos los rigidbodies en la escena.
FUNCIONES DE CLASE:
Raycast: static function Raycast (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Lanza un rayo contra todos los colliders en la escena. Devuelve true cuando el rayo intersecta algún collider. RaycastAll: static function RaycastAll (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : RaycastHit[] Lanza un rayo a través de la escena y devuelve todos los choques. LineCast: static function Linecast (start : Vector3, end : Vector3, layerMask : int = kDefaultRaycastLayers) : boolean Devuelve true si hay algún collider intersectando la línea entre start y end. OverlapSphere: static function OverlapSphere (position : Vector3, radius : float, layerMask : int = kAllLayers) : Collider[] Devuelve un array con todos los colliders que toquen o estén dentro de la esfera cuya posición y radio pasamos como parámetros. CapsuleCast: static function CapsuleCast (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Lanza una cápsula contra todos los colliders de la escena y devuelve información de contra qué ha chocado. Devuelve true cuando el sweep (barrido) de la cápsula choca con algún collider. SphereCast: static function SphereCast (origin : Vector3, radius : float, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean Proyecta una esfera contra todos los colliders de la escena y proporciona información sobre los que colisionan. Devuelve true si topa con alguno en su barrido. CapsuleCastAll: static function CapsuleCastAll (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layermask : int = kDefaultRaycastLayers) : RaycastHit[]
Es como Physics.CapsuleCast, pero devolviendo todos los colliders que la cápsula intercepta en su barrido e información sobre los mismos. Devuelve un array con todos esos colliders. SphereCastAll: static function SphereCastAll (origin : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : RaycastHit[] Como physics.SphereCast, pero devolviendo todos los colliders que colisionen. CheckSphere: static function CheckSphere (position : Vector3, radius : float, layerMask : int = kDefaultRaycastLayers) : boolean Devuelve true si hay algún collider tocando la esfera definida por position y radius en coordenadas globales. CheckCapsule: static function CheckCapsule (start : Vector3, end : Vector3, radius : float, layermask : int = kDefaultRaycastLayers) : boolean Devuelve true si hay algún collider tocando la cápsula definida por el eje que va de start a end y que tiene el radio radius, en coordenadas globales. IgnoreCollision: static function IgnoreCollision (collider1 : Collider, collider2 : Collider, ignore : boolean = true) : void Hace que el sistema de detección de colisiones ignore todas las colisiones entre collider1 y collider2. IgnoreLayerCollision: static function IgnoreLayerCollision (layer1 : int, layer2 : int, ignore : boolean = true) : void Hace que el sistema de detección de colisiones ignore todas las colisiones entre cualquier collider en layer1 y otros en layer2. GetIgnoreLayerCollision: static function GetIgnoreLayerCollision (layer1 : int, layer2 : int) : boolean Booleano que indica si las colisiones entre layer1 y layer2 están siendo ignoradas.
Quaternion VARIABLES
eulerAngles: var eulerAngles : Vector3 Devuelve la representacion en ángulos euler de la rotacion.
FUNCIONES:
ToAngleAxis: function ToAngleAxis (out angle : float, out axis : Vector3): void Convierte una rotación en una representación de angle-axis (ángulo de eje) SetFromRotation: function SetFromToRotation (fromDirection : Vector3, toDirection : Vector3) : void Crea una rotación que rota desde fromDirection hasta toDirection. ToString: function ToString () : String Devuelve un string formateado del Quaternion.
VARIABLES DE CLASE:
identity: static var identity : Quaternion Sería el equivalente a no rotation. El transform quedará perfectamente alineado con el mundo o con los ejes del padre.
FUNCIONES DE CLASE:
AngleAxis: static function AngleAxis (angle : float, axis : Vector3) : Quaternion Crea una rotación que rota los ángulos que le pasemos como primer parámetro con respecto al eje que le pasamos como segundo. FromToRotation: static function FromToRotation (fromDirection : Vector3, toDirection : Vector3) : Quaternion Crea una rotación que rota desde el primer parámetro al segundo.
Slerp: static function Slerp (from : Quaternion, to : Quaternion, t : float) : Quaternion Interpola esfériamente del primer al segundo parámetro durante el tercero. RotateTowards: static function RotateTowards (from : Quaternion, to : Quaternion, maxDegreesDelta : float) : Quaternion Efectúa una rotación entre el primer parámetro y el segundo. Esto es esencialmente lo mismo que Quaternion.Slerp, pero aquí la función se aregura de que la velocidad angular nunca exceda de la marcada en maxDegreesDelta. Angle: static function Angle (a : Quaternion, b : Quaternion) : float Devuelve el ángulo en grados entre dos rotaciones dadas. Euler: static function Euler (x : float, y : float, z : float) : Quaternion Devuelve en quaterniones una rotación pasada como parámetro en grados euler.
Random VARIABLES DE CLASE:
seed: static var seed : int Coloca la semilla para el generador de números aleatorios. value: static var value : float Devuelve un número aleatorio entre 0.0 (inclusive) y 1.0 (inclusive). insideUnitSphere: static var insideUnitSphere : Vector3 Devuelve un punto aleatorio dentro de una esfera con radio 1. insideUnitCircle: static var insideUnitCircle : Vector2 Devuelve un punto aleatorio dentro de un círculo con radio 1. onUnitSphere: static var onUnitSphere : Vector3 Devuelve un punto aleatorio sobre la superficie de una esfera con radio 1. rotation: static var rotation : Quaternion Devuelve una rotación aleatoria
FUNCIONES DE CLASE:
Range: static function Range (min : float, max : float) : float Devuelve un float (o un int, si se prefiere) aleatorio entre un min (inclusive) y max (inclusive).
Ray VARIABLES:
origin: var origin : Vector3 El punto de origen del rayo. direction: var direction : Vector3 La dirección del rayo.
FUNCIONES:
Ray: static function Ray (origin : Vector3, direction : Vector3) : Ray Crea un rayo que empieza en origin a lo largo de direction. GetPoint: function GetPoint (distance : float) : Vector3 Devuelve un punto situado tantas unidades como le pasemos en el parámetro a lo largo del rayo. ToString: function ToString () : String Devuelve un string formateado para este rayo.
RaycastHit VARIABLES:
point: var point : Vector3 El punto de impacto en coordenadas globales donde el rayo golpea el collider normal: var normal : Vector3 El normal de la superficie que golpea el rayo. barycentricCoordinate: var barycentricCoordinate : Vector3 La coordenada baricéntrica del triángulo que golpeamos. distance: var distance : float
La distancia desde el origen del rayo hasta el punto de impacto. triangleIndex: var triangleIndex : int El índice del triángulo que ha sido golpeado. textureCoord: var textureCoord : Vector2 La coordenada de la textura UV en el punto de impacto. textureCoord2: var textureCoord2 : Vector2 Las coordenadas de la textura uv secundaria. lightmapCoord: var lightmapCoord : Vector2 La coordinada del lightmap de uv en el punto de impacto. collider: var collider : Collider El collider que fue golpeado. rigidbody: var rigidbody : Rigidbody El rigidbody del collider que ha sido golpeado. transform: var transform : Transform El Transform del rigidbody o collider que ha sido golpeado.
Rect: VARIABLES:
x: var x : float Coordenada izquierda del rectángulo (a lo largo del eje X). y: var y : float Coordenada superior del rectángulo. width: var width : float Anchura del rectángulo. height: var height : float Altura del rectángulo. xMin: var xMin : float Coordenada izquierda del rectángulo. Cambiando este valor conservamos el lado derecho del rectángulo (así width cambia también)
yMin: var yMin : float Coordenada superior del rectángulo. Cambiando este valor conservamos el lado inferior del rectángulo (así height cambiará también) xMax: var xMax : float Coordenada derecha del rectánculo. Cambiando este valor seguimos conservando el lado izquierdo del rectángulo, por lo que la anchura cambiará también. yMax: var yMax : float Coordenada inferior del rectángulo. Cambiando este valor seguimos conservando el lado superior del rectángulo, así que la altura cambiará también.
FUNCIONES:
Rect: static function Rect (left : float, top : float, width : float, height : float) : Rect Crea un nuevo rectángulo. Contains: function Contains (point : Vector2) : boolean Devuelve true si los componentes x e y del parámetro point conforman un punto dentro del rectángulo.
FUNCIONES DE CLASE:
MinMaxRect: static function MinMaxRect (left : float, top : float, right : float, bottom : float) : Rect Crea un rectángulo entre min/max valores de coordenadas.
Renderer VARIABLES:
enabled: var enabled : boolean Hace el objeto visible (true) o invisible (false). castShadows: var castShadows : boolean ¿Proyecta sombras este objeto?
receiveShadows: var receiveShadows : boolean ¿Recibe sombras este objeto? material: var material : Material El material de nuestro objeto. Modificar esta variable sólo cambiará el material para este objeto. Si el material que asignamos a nuestro objeto está siendo usado para otros renders, se clonará el material compartido y se asignará una copia de dicho material para nuestro objeto. sharedMaterial: var sharedMaterial : Material Hace referencia al material que nuestro objeto comparte con otros. sharedMaterials: var sharedMaterials : Material[] Devuelve un array con todos los materials compartidos de este objeto, a diferencia de sharedMaterial y material, que sólo devuelven el primer material usado si el objeto tiene más de uno. materials: var materials : Material[] Devuelve un array con todos los materiales usados por el renderer. bounds: var bounds : Bounds Es una variable de sólo lectura que indica los límites del volumen del renderer. lightmapIndex: var lightmapIndex : int El índice del lightmap aplicado a este renderer. El índice se refiere al array de lightmaps que está en la clase LightmapSettings. isVisible var isVisible : boolean Variable de sólo lectura que indica si el renderer es visible en alguna cámara.
FUNCIONES:
OnBecameVisible: function OnBecameVisible () : void Esta función es llamada cuando el objeto se vuelve visible para alguna cámara. OnBecameInvisible: unction OnBecameInvisible () : void Es llamada cuando el objeto ya no es visible por ninguna cámara.
RenderSettings VARIABLES DE CLASE:
fog: static var fog : boolean ¿Está habilitada la niebla? fogMode: static var fogMode : FogMode Modo de niebla a usar. fogColor: static var fogColor : Color El color de la niebla. fogDensity: static var fogDensity : float La densidad de la niebla exponencial. fogStartDistance: static var fogStartDistance : float La distancia inicial de la niebla lineal. fogEndDistance: static var fogEndDistance : float La distancia final de la niebla lineal (sólo para modo Linear). ambientLight: static var ambientLight : Color Color de la luz de ambiente de la escena. haloStrength: static var haloStrength : float Tamaño del halo de luz. Para cualquier luz, el tamaño del halo es este valor multiplicado por Light.range. flareStrength: static var flareStrength : float La intensidad de los destellos de la luz en la escena. skybox: static var skybox : Material El skybox global en uso.
RenderTextures VARIABLES:
width: var width : int
La anchura de la textura renderizada en píxeles. height: var height : int La altura de la textura renderizada en píxeles. depth: var depth : int La precisión en bits de la profundidad del búfer de la render texture (son soportados los valores 0, 16 y 24) format: var format : RenderTextureFormat El formato de la render texture. useMipMap: var useMipMap : boolean Por defecto, las render textures no tienen mipmaps. Si establecemos esta variable en true, se generarán niveles de mipmap asociados. isCubemap: var isCubemap : boolean Si está habilitada, esta render texture será usada como Cubemap.
FUNCIONES:
RenderTextures: static function RenderTexture (width : int, height : int, depth : int, format : RenderTextureFormat) : RenderTexture Crea un nuevo objeto RenderTexture, que es creado con anchura y altura, con un buffer de profundidad y en un determinado formato. Create: function Create () : boolean Esta función la que crea en realidad la RenderTexture. Release: function Release () : void Esta función libera los recursos de hardware usados por la render texture. La texture en sí no es destruida, y será automáticamente creada otra vez cuando se use. IsCreated: function IsCreated () : boolean Indica si la render texture se ha creado realmente o no. DiscardContents: function DiscardContents () : void Descarta el contenido de la render texture.
SetGlobalShaderProperty: function SetGlobalShaderProperty (propertyName : String) : void Asigna esta render texture como la propiedad shader global llamada en el parĂĄmetro propertyName.
VARIABLES DE CLASE:
active: static var active : RenderTexture Se refiere a la render texture activa.
FUNCIONES DE CLASE:
GetTemporary: static function GetTemporary (width : int, height : int, depthBuffer : int = 0, format : RenderTextureFormat = RenderTextureFormat.Default) : RenderTexture Asigna una render texture temporal. ReleaseTemporary: static function ReleaseTemporary (temp : RenderTexture) : void Libera una textura temporal asignada con GetTemporary.
Rigidbody VARIABLES:
velocity: Var velocity : Vector3 Representa a travĂŠs de un vector la velocidad del rigidbody. angularVelocity: Var angularVelocity : Vector3 Vector que representa la velocidad angular del rigidbody. drag: Var drag : float Indica la resistencia al movimiento de un objeto y por lo tanto se usa para enlentecer dicho objeto.
angularDrag: var angularDrag : float Indica la resistencia angular del objeto. Puede ser usado para enlentecer la rotación de un objeto. useGravity: Var useGravity : boolean Booleano que controla si la gravedad afecta a este rigidbody. isKinematic: Var isKinematic : boolean Controla si las físicas afectan al rigidbody. freezeRotation: Var freezeRotation : Boolean Controla si las físicas cambiarán la rotación del objeto. constraints: Var constraints : RigidbodyConstraints Controla qué grados de libertad están permitidos para la simulación de nuestro rigidbody. collisionDetectionMode: var collisionDetectionMode : CollisionDetectionMode Permite establecer el modo de detección de colisiones del rigidbody. centerOfMass: var CenterOfMass : Vector3 Representa el centro de masa relativo al origen del transform. worldCenterOfMass: var worldCenterOfMass : Vector3 Esta variable nos indica el centro de masa/gravedad de un rigidbody en el espacio global. detectCollisions: Var detectCollisions : Boolean Indica si la detección de colisiones está habilitada (por defecto, sí). interpolation: Var interpolation : RigidbodyInterpolation Nos permite suavizar el efecto de correr físicas en un framerate fijo. sleepVelocity: var sleepVelocity : float La velocidad lineal, por debajo de la cual los objetos empiezan a dormir. sleepAngularVelocity: var sleepAngularVelocity : float La velocidad angular, por debajo de los cuales los objetos empiezan a dormirse. maxAngularVelocity: Var maxAngularVelocity : float Representa la maxima velocidad angular del rigidbody.
FUNCIONES:
SetDensity: Function SetDensity (density : float) : void Nos permite asignar una masa en base al collider vinculado, lo cual es útil para asignar una masa en unos valores acordes al tamaño de los colliders. AddForce: function AddForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void Esta función añade una fuerza al rigidbody. Como resultado de esto el rigidbody comenzará a moverse. AddRelativeForce: function AddRelativeForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void Añade una fuerza al rigidbody relativa al sistema local de coordenadas de dicho rigidbody. AddTorque: function AddTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void Esta función añade una torsión al rigidbody. Como resultado el rigidbody empezará a girar alrededor del eje de torsión. AddRelativeTorque: function AddRelativeTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void Esta función añade una fuerza de torsión relativa a las coordenadas locales del rigidbody. AddForceAtPosition: Function AddForceAtPosition (force : Vector3, position : Vector3, mode : ForceMode = ForceMode.Force) : void Aplica fuerza a un rigidbody en una posición determinada. AddExplosionForce: Function AddExplosionForce (explosionForce : float, explosionPosition : Vector3, explosionRadius : float, upwardsModifier : float = 0.0F, mode : ForceMode = ForceMode.Force) : void Aplica una fuerza al rigidbody que simula el efecto de una explosión. La fuerza de la explosión decaerá de manera lineal con la distancia del rigidbody.
MovePosition: Function MovePosition (position : Vector3) : void Mueve de posición el rigidbody. MoveRotation unction MoveRotation (rot : Quaternion) : void Rota el rigidbody. Sleep: Function Sleep () : void Fuerza al rigidbody a dormir al menos un frame. IsSleeping: Function IsSleeping () : Boolean Booleano que devuelve true si el rigidbody está durmiendo. WakeUp: Function WakeUp () : void Fuerza al rigidbody a despertarse. SweepTest: Function SweepTest (direction : Vector3, hitInfo : RaycastHit, distance : float = Mathf.Infinity) : Boolean Devuelve true cuando un barrido del rigidbody intercepta algún collider. Almacena información del primer collider que intercepta. SweepTestAll: Function SweepTestAll (direction : Vector3, distance : float = Mathf.Infinity) : RaycastHit[] Es parecida a la anterior, pero devolviendo un array de tipo RaycastHit con toda la información de todos los colliders hallados en el camino. OnCollisionEnter: function OnCollisionEnter (collisionInfo : Collision) : void Es llamada cuando nuestro rigidbody/collider toca otro rigidbody/collider. OnCollisionExit: function OnCollisionExit (collisionInfo : Collision) : void Es llamada cuando nuestro collider/rigidbody deja de tocar otro rigidbody/collider. OnCollisionStay: function OnCollisionStay (collisionInfo : Collision) : void Es llamada una vez cada frame para cada collider/rigidbody que esté tocando nuestro rigidbody/collider.
Screen VARIABLES DE CLASE:
resolutions: static var resolutions : Resolution[] Devuelve todas las resoluciones de pantalla completa soportadas por el monitor. currentResoluction: static var currentResolution : Resolution Variable de sólo lectura que devuelve la resolución de pantalla actual. showCursor: static var showCursor : boolean ¿Debe el cursor ser visible? lockCursor: static var lockCursor : boolean ¿Debe el cursor ser bloqueado? whidth: static var width : int La anchura actual de la ventana en la pantalla en píxeles (sólo lectura). height: static var height : int La altura actual de la ventana en la pantalla en píxeles (sólo lectura) fullScreen: static var fullScreen : boolean ¿Está el juego corriendo en pantalla completa? sleepTimeout: static var sleepTimeout : float Un ajuste de ahorro de energía, permitiendo la atenuación de la pantalla algún tiempo después de la interacción del último usuario activo.
FUNCIONES DE CLASE:
SetResolution: static function SetResolution (width : int, height : int, fullscreen : boolean, preferredRefreshRate : int = 0) : void Cambia la resolución de pantalla.
Shader VARIABLES:
isSupported: var isSupported : boolean Bool de sólo lectura que indica si un determinado shader puede correr en la tarjeta gráfica del usuario.
FUNCIONES DE CLASE:
Find: static function Find (name : String) : Shader Encuentra un shader con el nombre dado.
SpringJoint VARIABLES:
spring: var spring : float La fuerza del spring usada para mantener los dos objetos juntos. damper: var damper : float La fuerza de amortiguación usada para amortiguar la fuerza del spring. minDistance: var minDistance : float La distancia mínima entre los rigidbodies relativa a su distancia inicial. maxDistance: var maxDistance : float La distancia máxima entre los rigidbodies relativa a su distancia inicial.
Texture VARIABLES:
width: var width: int Variable de sólo lectura que indica la anchura de la textura en píxeles. height: var height : int Variable de sólo lectura que indica la alturade la textura en píxeles. filterMode: var filterMode : FilterMode Modo de filtrado de la textura. anisoLevel: var anisoLevel : int Nivel de filtro anisotrópico de la textura. wrapMode: var wrapMode : TextureWrapMode Modo de envoltura de la textura. Texture2D VARIABLES:
mipmapCount: var mipmapCount : int Variable de sólo lectura que indica cuántos niveles de mipmap hay en la textura. textureFormat: var format : TextureFormat El formato de los datos de los píxeles en la textura (sólo lectura).
FUNCIONES:
Texture2D: static function Texture2D (width : int, height : int) : Texture2D Crea una nueva textura vacía. Esta tendrá un tamaño dado por anchura y altura, con un formato de textura ARGV32 y con mipmaps. SetPixel: function SetPixel (x : int, y : int, color : Color) : void Función que nos permite indicar el color de los píxeles en las coordenadas que le pasamos. GetPixel: function GetPixel (x : int, y : int) : Color Devuelve el color del píxel que se halla en las coordenadas dadas. GetPixelBilinear: function GetPixelBilinear (u : float, v : float) : Color function GetPixelBilinear (u : float, v : float) : Color SetPixels: function SetPixels (colors : Color[], miplevel : int = 0) : void Establece un bloque de colores de píxeles. Esta función toma un array de colores y cambia los colores de los píxeles del nivel mip entero de la textura. SetPixels32: function SetPixels32 (colors : Color32[], miplevel : int = 0) : void Establece un bloque de colores de píxeles. Esta función toma un array de tipo Color32 y cambia los colores de los píxeles del nivel de mip entero de la textura. LoadImage: function LoadImage (data : byte[]) : boolean Carga una imagen de tipo JPG o PNG desde un array de bytes. GetPixels: function GetPixels (miplevel : int = 0) : Color[] Esta función devuelve un array de colores de píxeles del nivel de mip entero de la textura. GetPixels32: function GetPixels32 (miplevel : int = 0) : Color32[]
Obtiene un bloque de colores de píxeles en formato Color32. Apply: function Apply (updateMipmaps : boolean = true, makeNoLongerReadable : boolean = false) : void Aplica en la práctica todos los cambios previamente efectuados con las funciones SetPixel y SetPixels. Resize: function Resize (width : int, height : int, format : TextureFormat, hasMipMap : boolean) : boolean Redimensiona la textura, y en concreto modifica la anchura, altura, formato y en ocasiones crea mipmaps. ReadPixels: function ReadPixels (source : Rect, destX : int, destY : int, recalculateMipMaps : boolean = true) : void Lee píxeles de pantalla dentro de los datos de la textura guardada. EncodeToPNG: function EncodeToPNG () : byte[] Codifica esta textura en formato PNG. El array de bytes que devuelve es el archivo PNG.
Time VARIABLES DE CLASE:
time: static var time : float Es el tiempo en segundos desde el inicio del juego (sólo lectura). timeSinceLevelLoad: static var timeSinceLevelLoad : float Tiempo en segundos desde que el último level ha sido cargado (sólo lectura) deltaTime: static var deltaTime : float El tiempo en segundos que se tardó en completar el último frame (sólo lectura). fixedTime: static var fixedTime : float El tiempo desde que el último FixedUpdate comenzó (sólo lectura). maximumDeltaTime: static var maximumDeltaTime : float El máximo tiempo que un frame puede tomar. smoothDeltaTime: static var smoothDeltaTime : float
Un Time.deltaTime suavizado. timeScale: static var timeScale : float La escala en la cual el tiempo pasa. frameCount: static var frameCount : int El número total de frames que han pasado (sólo lectura) realtimeSinceStartup: static var realtimeSinceStartup : float El tiempo real en segundos desde que el juego empezó (solo lectura). captureFramerate: static var captureFramerate : int Si está establecida en un valor superior a 0, el tiempo avanzará en (1.0 / captureFramerate) por frame sin tener en cuenta el tiempo real.
Transform VARIABLES:
position: var position : Vector3 Indica en qué punto del espacio global se ubica nuestro transform. localPosition: var localPosition : Vector3 Indica la posición del transform al que dicha variable pertenece, calculada en términos relativos al transform del que aquél depende. eulerAngles: var eulerAngles : Vector3 Indica la rotación del transform en grados. localEulerAngles: var localEulerAngles : Vector3 Se refiere a la rotación en grados de un transform con relación a la que tiene el transform del cual depende en una relación hijo/padre. rotation: var rotation : Quaternion Contiene la rotación del transform en el mundo global almacenada en quaterniones. localRotation: var localRotation : Quaternion Indica la rotación en quaterniones de un transform relativa a la rotación de su padre. right: var right : Vector3
El eje X de las coordenadas globales del transform. up: var up : Vector3 El eje Y de las coordenadas globales del transform. forward: var forward : Vector3 El eje Z de las coordenadas globales del transform. localScale: var localScale : Vector3 Permite consultar o establecer la escala de un transform en relación con la de su padre. parent: var parent : Transform Hace referencia al padre del transform. root: var root : Transform Hace referencia al transform más alto de la jerarquía. childCount: var childCount : int Indica el número de hijos que tiene un transform. lossyScale: var lossyScale : Vector3 Representa la escala global del objeto.
FUNCIONES:
Translate: function Translate (translation : Vector3, relativeTo : Space = Space.Self) : void Esta función nos permite mover el transform en la dirección y distancia indicados en el parámetro de tipo Vector3 traslation. El segundo parámetro nos permite decidir si el transform se moverá según sus propias coordenadas (locales) o lo hará en base al espacio global. Rotate: function Rotate (eulerAngles : Vector3, relativeTo : Space = Space.Self) : void Esta función permite rotar un transform. RotateAround: function RotateAround (point : Vector3, axis : Vector3, angle : float) : void
Esta función nos permite que nuestro transform rote alrededor de un punto (u objeto situado en ese punto), como si orbitara. LookAt: function LookAt (target : Transform, worldUp : Vector3 = Vector3.up) : void Rotar nuestro transform hacia un objetivo. TransformDirection: function TransformDirection (direction : Vector3) : Vector3 Toma como único parámetro la dirección local de un transform almacenada en un Vector3 y la convierte en dirección global, devolviéndola en otro Vector3. InverseTransformDirection: function InverseTransformDirection (direction : Vector3) : Vector3 Transforma una dirección global en dirección local. TransformPoint: function TransformPoint (position : Vector3) : Vector3 Tansforma una posición de local a global. InverseTransformPoint: function InverseTransformPoint (position : Vector3) : Vector3 Transforma la posición de un transform dado del espacio global al espacio local. DetachChildren: function DetachChildren () : void Función que sirve para desparentar los hijos. Find: function Find (name : String) : Transform Con esta función podemos buscar por su nombre -y en su caso acceder- a un transform hijo de nuestro transform. IsChildOf: function IsChildOf (parent : Transform) : boolean Devuelve true si el transform que hace la pregunta es hijo, "nieto" o incluso idéntico al transform parent que pasamos como parámetro.
Vector3 VARIABLES:
x: var x : float Componente x del vector. y: var y : float Componente y del vector.
z: var z : float Componente z del vector. this[index:int]: var this[index : int] : float Otra forma de acceder a los componentes del vector, a través del índice 0, 1 y 2. normalized: var normalized : Vector3 Devuelve un vector con la misma dirección que el original, pero con magnitud 1. magnitude: var magnitude : float Devuelve la longitud o tamaño de un vector. sqrMagnitude: var sqrMagnitude : float Devuelve el cuadrado de la longitud de un vector.
VARIABLES DE CLASE: zero: static var zero : Vector3 Equivale a Vector3(0,0,0). one: static var one : Vector3 Equivale a Vector3(1,1,1). forward: static var forward : Vector3 Equivale a Vector3(0,0,1). up: static var up : Vector3 Equivale a Vector3(0,1,0). right: static var right : Vector3 Equivale a Vector3(1,0,0).
FUNCIONES: Vector3 (constructor): static function Vector3 (x : float, y : float, z : float) : Vector3 Crea un nuevo Vector3. Scale: function Scale (scale : Vector3) : void Multiplica el Vector3 que llama la función por el Vector3 que le pasamos a dicha función como parámetro.
Normalize: function Normalize () : void Hace que el Vector3 que la llama pase a tener una magnitude de 1, conservando la misma direcci贸n. ToString: function ToString () : String Devuelve nuestro vector convertido en un string y formateado.
FUNCIONES DE CLASE: Lerp: static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3 Esta funci贸n interpola linealmente entre dos vectores, desde from hacia to en el valor t. Slerp: static function Slerp (from : Vector3, to : Vector3, t : float) : Vector3 Esta funci贸n interpola esf茅ricamente entre dos vectores, desde from hacia to en el valor t.