Jump to content

Ele

Fosiles
  • Content Count

    12
  • Joined

  • Last visited

Community Reputation

14 Good

About Ele

  • Rank
    Iniciado

Recent Profile Visitors

628 profile views
  1. Primero que nada debes saber que esto no es una característica o función de Unity en realidad se trata de una TÉCNICA de optimización. En que consiste?Esta técnica consiste en instanciar un numero determinado de objetos al iniciar o cargar el nivel, luego desactivarlos para activarlos parcial o totalmente. Y esto tiene un impacto positivo en el rendimiento del videojuego ya que es mas barato en recursos activar y desactivar un objeto ya instanciado, que instanciar un objeto nuevo y destruirlo posteriormente. OJO que sea mas barato en recursos no quiere decir que deba usarse sin medida, ya que al activar el objeto el motor debe renderizar nuevamente el objeto como si de un objeto nuevo se tratase, pero con esto nos ahorramos la carga del objeto en memoria. Veamos un poco de código para aplicar esta técnica… 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144using System.Collections.Generic;using UnityEngine; namespace EMCore{ public abstract class PooledObject : MonoBehaviour, IPoolableObject { public static PooledObject current; public GameObject pooledObject; public bool willGrow = true; public int pooledAmount = 20; public List<GameObject> pooledObjects; public virtual void Awake() { current = this; } public virtual void Start() { pooledObjects = new List<GameObject>(); for (int i = 0; i < pooledAmount; i++) { GameObject obj = (GameObject)Instantiate(pooledObject); obj.transform.parent = transform.parent; obj.SetActive(false); pooledObjects.Add(obj); } } public virtual GameObject GetPooledObject(Vector3? position = null, Quaternion? rotation = null) { Vector3 _pos = Vector3.zero; Quaternion _rot = Quaternion.identity; if (position.HasValue) _pos = position.Value; if (rotation.HasValue) _rot = rotation.Value; for (int i = 0; i < pooledObjects.Count; i++) { if (pooledObjects == null) { GameObject obj = (GameObject)Instantiate(pooledObject, _pos, _rot); obj.transform.parent = transform.parent; if (obj) { PooledItem pooledItem = obj.GetComponent<PooledItem>(); if (pooledItem && pooledItem.noParent) { obj.transform.parent = null; } } obj.SetActive(false); pooledObjects = obj; return pooledObjects; } if (!pooledObjects.activeInHierarchy) { pooledObjects.transform.position = _pos; pooledObjects.transform.rotation = _rot; pooledObjects.transform.parent = transform.parent; if (pooledObjects) { PooledItem pooledItem = pooledObjects.GetComponent<PooledItem>(); if (pooledItem && pooledItem.noParent) { pooledObjects.transform.parent = null; } } return pooledObjects; } } if (willGrow) { GameObject obj = (GameObject)Instantiate(pooledObject, _pos, _rot); obj.transform.parent = transform.parent; if (obj) { PooledItem pooledItem = obj.GetComponent<PooledItem>(); if (pooledItem && pooledItem.noParent) { obj.transform.parent = null; } } pooledObjects.Add(obj); return obj; } return null; } GameObject IPoolableObject.pooledObject { get { return this.pooledObject; } set { this.pooledObject = value; } } bool IPoolableObject.willGrow { get { return this.willGrow; } set { this.willGrow = value; } } int IPoolableObject.pooledAmount { get { return this.pooledAmount; } set { this.pooledAmount = value; } } List<GameObject> IPoolableObject.pooledObjects { get { return this.pooledObjects; } set { this.pooledObjects = value; } } }}Podemos crear una clase la cual herede de esta y la adjuntamos en un objeto “vacío” en la escena y esta sera la encargada de manejar o gestionar la lista (variable de clase llamada “pooledObjects“) de objetos instanciados. Como podrán notar esta clase es abstracta, lo que nos obligara a usarla como padre de otra clase, es decir como dije anteriormente se debe crear una clase (BulletsManager por ejemplo) que herede de esta para usarla. Ademas también implementa una interface llamada “IPoolableObject” lo cual nos servirá, como mencione en el articulo “Porque usar POO(Programacion Orientada a Objetos) en la programación de videojuegos”, para tratar varias clases que también la implementen (BulletsManager y MuzzleManager) sin importar que clase sea, esto nos sera útil para reciclar código. También esta clase implementa un patrón “Singleton” de manera que se puede acceder a la instancia a través del miembro estático “current” de la siguiente manera, por ejemplo: 12BulletsManager bm = BulletsManager.current;Debug.Log(bm.pooledObjects.Count());La clase PooledObject también usa una clase llamada “PooledItem” que la muestro a continuación: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172using UnityEngine; namespace EMCore{ public abstract class PooledItem : MonoBehaviour, IPoolableItem { public bool autoDisable = true; public float timeToDisable = 2f; public bool noParent = false; public virtual void OnEnable() { if (autoDisable) { Invoke("Disable", timeToDisable); } if (noParent && transform.parent) { transform.parent = null; } } public virtual void Disable() { gameObject.SetActive(false); } public virtual void OnDisable() { if (autoDisable) { CancelInvoke(); } } bool IPoolableItem.autoDisable { get { return this.autoDisable; } set { this.autoDisable = value; } } float IPoolableItem.timeToDisable { get { return this.timeToDisable; } set { this.timeToDisable = value; } } bool IPoolableItem.noParent { get { return this.noParent; } set { this.noParent = value; } } }}Como pueden notar esta clase es abstracta y también implementa una interface llamada “IPoolableItem“, de igual forma se debera crear una clase que herede de esta para poder usarla. Ejemplifiquemos un poco como se podrían utilizar estas clases, imaginemos el entorno tal y como lo plantea la siguiente imagen:No voy a detenerme mucho a explicar el entorno porque es sencillo, básicamente es un FPSController con una capsula, donde el objeto weapon es un rectángulo, tag_bullet_spawn es un objeto vació de solo referencia, también podemos ver un objeto llamado “PoolingManager” en este podemos adjuntar todas nuestras clases que hereden de “PooledObject” de manera de tener nuestra escena organizada. Crearemos una clase llamada “BulletsManager” de la siguiente manera: 12345678910using System.Collections.Generic;using UnityEngine; namespace EMCore{ public class BulletsManager : PooledObject { }}Esta clase contendrá el listado de balas en la escena y se encargara de activarlas cuando el usuario lo necesite, ademas de esto no necesita implementar mas nada para el funcionamiento básico de esta técnica ya que hereda de “PooledObject“, pero también pudieran incluir su propio código en ella para añadirle funcionalidad especifica para gestionar las balas, noten que al ser una clase singleton esta debe ser única en la escena esto nos facilitara acceder y ademas nos permitirá tener en un mismo sitio el encargado de las balas en toda la escena, es decir que todos los actores que disparen lo harán a través de esta única clase e instancia (punto a favor de la optimización!). Ahora crearemos una clase que se deberá adjuntar en la bala, es decir el objeto que se repetirá y reciclara: 12345678910111213using UnityEngine; namespace EMCore{ public class Bullet : PooledItem { public float speed = 2; void Update() { transform.Translate(transform.forward * speed, Space.World); } }}Esta clase hereda de la clase “PooledItem” esta sera la encargada de desactivar el objeto y también implementa una funcionalidad en particular para el movimiento continuo hacia adelante de la bala. Por ultimo necesitaremos el arma para ello crearemos una clase llamadaWeapon: 12345678910111213141516171819202122232425262728293031using UnityEngine;using EMCore; public class Weapon : MonoBehaviour{ public Transform tagSpawn; public float fireTime = 0.05f; private bool firing = false; void Start () { InvokeRepeating("Fire", fireTime, fireTime); } void Update() { firing = Input.GetButton("Fire1"); } public void Fire() { if (firing) { GameObject obj = BulletsManager.current.GetPooledObject(tagSpawn.position, tagSpawn.rotation); if (obj == null) return; obj.SetActive(true); } }}Esta clase usa el método InvokeRepeating para, como se puede intuir de su nombre, llamar otra función cada cierto tiempo, ademas como mencione anteriormente se llama a la única instancia de BulletsManagera través de su miembro estático y el método “GetPooledObject” lo que hará es darnos el siguiente objeto disponible en el listado, recuerden que al iniciar el script se instancian la cantidad especificada en la propiedad “pooledAmount” de “BulletsManager” y el “prefab” se especifica en la propiedad “pooledObject” de “BulletsManager” y a medida queInvokeRepeating llame al método Fire de “Weapon” se irán activando, ademas si coloca en true la propiedad willGrow, esta lista podrá aumentar a medida de la demanda, ya que si la demanda es mayor al lo que especifique en pooledAmount habrá momentos en que se interrumpirá el reciclado ya que al estar todos los objetos en el listado (pooledObjects) activos, la clase debe espera a que un objeto PooledItem (es decir la bala) se desactive a si mismo. Espero hayan entendido la explicación y les sirva, les dejo a continuacion las interfaces IPoolableObject, y IPoolableItem usadas en este articulo: IPoolableObject 1234567891011121314151617181920using System.Collections.Generic;using UnityEngine; namespace EMCore{ public interface IPoolableObject { GameObject pooledObject { get; set; } bool willGrow { get; set; } int pooledAmount { get; set; } List<GameObject> pooledObjects { get; set; } void Awake(); void Start(); GameObject GetPooledObject(Vector3? position = null, Quaternion? rotation = null); }}IPoolableItem 123456789101112131415namespace EMCore{ public interface IPoolableItem { bool autoDisable { get; set; } float timeToDisable { get; set; } bool noParent { get; set; } void OnEnable(); void Disable(); void OnDisable(); }}Ademas les dejo materiales extras donde podrán ver con mayor detalle esta técnica o complementar la información: Modulo de Unity:https://unity3d.com/es/learn/tutorials/modules/beginner/live-training-archive/object-pooling Blog Recomendado:https://gamedevn.wordpress.com/2015/09/27/optimizacion-rendimiento-videojuego-object-pool-pattern/ Cualquier duda, sugerencia o corrección dejen su comentario! Saludos… fuente: https://eleazarcelis.wordpress.com/2015/11/20/comprendiendo-el-concepto-de-object-pooling
  2. Saludos colegas y entusiastas de la programación, quiero iniciar este articulo diciéndoles de entrada que no voy a tocar todos los aspectos de la inteligencia artificial ni tampoco haré ejemplos extensos ya que si lo hiciera podría escribir un libro de al menos unas 500 paginas, al menos con mi conocimiento, estoy seguro que otros podrían escribir de mas paginas, ya que es un tema bastante extenso. Lo primero que debemos hacer (se aplica a todo en programación) es establecer el o los problemas, les recomiendo que busquen unaherramienta* para crear mapas mentales o tener un pizarron o mas simple un lápiz y un papel porque esto requerirá de unaLÓGICA ACERTADA, recuerden que lo que pretendemos hacer es crear una simulación de inteligencia, analicemos el siguiente mapa mental: Lo que primero debemos saber es que nuestro actor con AI deberá tener un comportamiento principal, este comportamiento definirá sus acciones y movilidad. Este actor también deberá tener varios estados por ejemplo “en persecucion” “default”(viene dado por el comportamiento, ejem.: patrullando, explorando,…),”bajo ataque”, “en alerta” y los que uds. consideren que pudiera aplicar, estos estados serán importante para informar a otros componentes que esta haciendo el actor. Lo segundo que deberemos definir es un área de acción en los casos de patrullaje y de defensa, esto sera necesario para definir la movilidad del actor. La movilidad de un actor puede gestionarse perfectamente a través de un navmesh, esto nos ahorrara mucho calculo. El navagent se encargara de encontrar una ruta hacia un punto este punto se puede definir en cualquier momento al igual que la velocidad a la que se dirigirá hacia él, y también la distancia de frenado, estos son 3 aspectos que nos harán la vida mas fácil. Ahora que tengamos esto listo podremos resolver nuestro siguiente problema y es quizás el punto mas importante en AI para videojuegos la percepción. La PercepciónLa percepción se podría definir como “el primer conocimiento de una cosa por medio de las impresiones que comunican los sentidos”. La percepción disparara comportamientos y acciones en nuestro actor con AI de allí su importancia en programarla. Lo primero que debemos definir son lossentidos que tendrá nuestro actor, como vieron en la imagen yo solo he definido la vista, el tacto y la audición, aunque pudieran definirse otros, inclusive sentidos de fantasía como la habilidad de percibir enemigos que estén detrás de las paredes por ejemplo. Empecemos por el menos complejo, el tacto. Digo que este sentido es el menos complejo ya que este ya viene preparado por uniti con los colliders, un simple OnCollisionEnter podrá notificar que algo o un enemigo ha sido detectado. Y esto nos llevara a un punto importante, ladireccion de dicha interacción, para ello podemos recurrir a la propiedad “contacts[0].point”* del objeto “Collision” que nos da OnCollisionEnter, con ello podemos calcular la dirección de dicha interaccion, por ejemplo: 123void OnCollisionEnter(Collision other) { touchDirection = (other.contacts[0].point - transform.position);}y como pasaría en la vida real, dicha interacción provocaría una reacción motora inmediata la cual seria un giro y evasión. Con la información recogida por el sentido ya podríamos ser capaces de rotar hacia la dirección nuestro actor y moverlo en sentido opuesto al contacto con algo como esto: 12345void Update() { transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(touchDirection ), Time.deltaTime * turnSpeed); float step = speed * Time.deltaTime; transform.position = Vector3.MoveTowards(transform.position, touchDirection, step);}Ya tenemos la ‘información recogida por el sentido‘ y la respuesta motora correspondiente, si por ejemplo estamos programando un shooter deberemos disparar o no en dirección al contacto o tomar alguna acción en consecuencia, y para ello nos apoyaríamos seguramente en otro sentido como la vista por ejemplo para poder apuntar o captar donde esta nuestro enemigo pero eso lo trataremos mas adelante, pasemos al siguiente sentido. La AudiciónPara este sentido pudiéramos usar un SphereCollider y activar isTrigger, de manera de usar su radio como radio de detección, es decir el actor podrá “escuchar” todo lo con este dentro de este radio, para ello podríamos usar la función OnTriggerStay(Collider other) para gestionar la información, por ejemplo: 123456public void OnTriggerStay(Collider other) { AudioSource audioSource = other.transform.root.GetComponent<AudioSource>(); // asumiendo que el componente audiosource estara siempre en la raiz del enemigo if(audioSource != null && audioSource.isPlaying){ // implementacion... }}de esta manera podría saber si el objeto dentro del rango de audición esta reproduciendo algún sonido e inclusive podríamos aplicar algunas atenuantes de sonido, por ejemplo el volumen, la obstrucción por parte de muros y otros objetos, e incluso otros sonidos cercanos. Como en el sentido anterior esta recepción de información de igual forma disparara una reacción motora solo que esta podría ser mas relajada, ya que normalmente la percepción de un sonido aumenta desde 0 a medida que el origen se acerca a la posición del actor, por ello la reacción seria mas de “curiosidad”, como en la vida real un sonido nos puede alertar que algo esta cerca y gracias a que tenemos dos oídos tenemos la habilidad de escuchar en stereo, eso, una sutil diferencia de decibeles entre un oído y el otro puede dar la dirección aproximada del origen del sonido y con ello podríamos ir a investigar. Podemos usar el mismo principio que aplicamos en el sentido anterior para rotar el actor debido a que estamos gestionando el sentido a través de un trigger podemos obtener la posición exacta de la fuente con algo parecido: 12345678public void OnTriggerStay(Collider other) { AudioSource audioSource = other.transform.root.GetComponent<AudioSource>(); // asumiendo que el componente audiosource estara siempre en la raiz del enemigo if(audioSource != null && audioSource.isPlaying){ // implementacion de atenuacion... soundDirection = (other.transform.position - transform.position); transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(soundDirection), Time.deltaTime * turnSpeed); }}inclusive si usamos un navmesh podríamos definir other.transform.position como punto de destino a fin de investigar oexplorar si el origen esta tras un muro y así tomar una acción en consecuencia bien sea apoyándose en la vista para confirmar que fue lo que causo el sonido o tomar una acción ciegamente por ejemplo disparar sin confirmación visual. Como pudimos notar tanto el sentido del tacto como el auditivo, pueden llevar al actor a apoyarse en el sentido de la vista para tomar una acción definitiva, si por ejemplo disparar o interactuar con el origen de la información, por esto he dejado este sentido de ultimo pero ahora debemos considerarlo. La VisiónPara la visión debemos establecer dos variables importantes el campo visual y el rango de visión estos son dos factores importantes para la detección visual de otra entidad en el videojuego. El campo visual lo expresaremos en grados y este sera el angulo con respecto al frente del actor, lo normal seria establecer un campo de visión de 90° esto nos dará un angulo total de 180° en frente de nuestro personaje. El rango de visión lo expresaremos con un float y sera el resultado de Vector3.Distance. Siempre es muy recomendable crear un GameControl o como lo quieran llamar, un script que gestione en general aspectos del juego o el nivel: 1234567891011121314public class GameControl : MonoBehaviour{ // implementacion singletone public static GameControl mainGameControl; public List<GameObject> actores = new List<GameObject>(); public void Awake() { if (mainGameControl == null) { mainGameControl = this; } }}como notaron tenemos una variable llamada actores, la que llenaremos a través de los diferentes scripts que tengamos para cargar enemigos o jugadores en la escena. De esta manera podemos tener una lista de los actores activos en la escena de manera de poder recurrir a esta para verificar la posición de cada enemigo y distancia del actor para saber si esta dentro del campo y rango visual, veamos el siguiente ejemplo: 1234567891011public void Update(){ IEnumerable<GameObject> actores = GameControl.mainGameControl.actores.Where(e => (e != null || e.transform != null || e.transform.root != null || e.transform.root.gameObject != null) && !e.GetInstanceID().Equals(gameObject.GetInstanceID()) && nameIsEnemy.Any(nk => e.transform.root.tag.Contains(nk) || e.transform.root.name.Contains(nk) ) && Vector3.Distance(transform.position, e.transform.root.position) < maxDistanceSight );}en el ejemplo anterior creamos un IEnumerable de los GameObjects que cumplen las condiciones dadas, existen en la escena, no es el mismo actor, también podemos usar un listado de strings llamado “nameIsEnemy” para definir que los objetos que contenga cierta palabra en el tag o en el nombre serán considerados como enemigos, y una variable llamada “maxDistanceSight” donde pudiéramos definir la distancia máxima de visión de manera de limitar el rango visual, noten también que estamos accediendo a la lista de actores del GameControl con la linea GameControl.mainGameControl.actores una de las ventajas de usar una implementacion singletone de un componente. Hasta ahora tenemos los actores activos que son enemigos potenciales y que están en rango visual, pero todavía no sabemos si están en campo de visión y si de hecho no están obstaculizados, no sabemos aun si son visibles o no. Yo he creado una clase que nos ayudara a filtrar esos actores para encontrar el enemigo al que se le deberá poner atención, el que cumpla ciertas condiciones que antes mencionamos, que no este obstaculizado y este en el campo de visión 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778public class Enemies : System.Object{ [SerializeField] public Vector3 point { get; set; } [SerializeField] public float distance { get; set; } [SerializeField] public float angle { get; set; } [SerializeField] public bool isVisible { get { return _isVisible; } set { _isVisible = value; } } public RaycastHit raycastHitInformation { get; private set; } AIEnums.Senses _sensoryModalities; public AIEnums.Senses sensoryModalities { get { return _sensoryModalities; } set { __sensoryModalities = value; _sensoryModalities = value; } } private GameObject _gameObject; public GameObject gameObject; { get { return _gameObject; } set { _gameObject = value; if (_monoBehaviour != null && _maxDistanceSight > 0) { this.isVisible = CheckForVisibility(_monoBehaviour, _maxDistanceSight); } } } public AIEnums.Senses __sensoryModalities; public bool _isVisible; private MonoBehaviour _monoBehaviour { get; set; } private float _maxDistanceSight { get; set; } public Enemies() { } public Enemies(MonoBehaviour monoBehaviour, float maxDistanceSight) { this._monoBehaviour = monoBehaviour; this._maxDistanceSight = maxDistanceSight; } public bool CheckForVisibility(MonoBehaviour monoBehaviour, float maxDistanceSight) { Vector3 itemFollowingPos = Vector3.zero; Vector3 actorPos = Vector3.zero; itemFollowingPos = gameObject.transform.position; actorPos = monoBehaviour.transform.position; Vector3 enemyDir = (itemFollowingPos - actorPos); RaycastHit hit; if (Physics.Raycast(actorPos, enemyDir, out hit, maxDistanceSight)) { if (gameObject == hit.transform.gameObject) { raycastHitInformation = hit; return true; } } raycastHitInformation = hit; return false; }}como pueden notar he creado un campo llamado gameObject que contendrá el gameObject del enemigo y en el cuerpo del set de este campo se ejecutara un método llamado “CheckForVisibility” el cual envía un rayo a dicho enemigo para verificar si hace hit es porque no esta obstaculizado y devuelve true o en caso contrario devuelve false y lo guarda en el campo llamado isVisible, ademas tenemos las propiedades point, distance y angle que nos serán de utilidad mas tarde. Volviendo a nuestro componente de AI debemos recabar la informacion que nos interesa de cada actor: 12345678IEnumerable<Enemies> enemies = actores.Select(x => new Enemies(monoBehaviour, maxDistanceSight){ gameObject = x, distance = Vector3.Distance(monoBehaviour.transform.position, x.transform.root.position), angle = Vector3.Angle(monoBehaviour.transform.gameObject.transform.forward, (x.transform.position - monoBehaviour.transform.gameObject.transform.position)), point = x.transform.position, sensoryModalities = AIEnums.Senses.Vision,});entonces tendremos una lista con cada enemigo, la distancia, el angulo en relación al frente de nuestro actor y la posición del enemigo. Con el listado como esta y asumiendo que contenga algún ítem, solo bastaría ordenar la lista de la siguiente manera con linq: 12345List<Enemies> enemysInRange = new List<Enemies>();if (enemies.Count() > 0){ enemysInRange = enemies.OrderBy(e => e.isVisible).ThenBy(e => e.angle).ThenBy(e => e.distance).ToList();}ordenamos primero si isVisible esta en true primero, luego los que tengan menor valor en la propiedad angle y por ultimo los que tengan menos distancia. Ya de esta manera lo que debemos es obtener el primer elemento de esta coleccion 1234567891011if (enemysInRange.Count > 0){ Enemies enemy = enemysInRange.First(); if (enemy.isVisible) { if(enemy.angle < FOV) { } }}ya acá podemos saber si el enemigo esta en el campo visual, donde FOV es como les comente anteriormente el campo visual y esta expresado en grados, este sera el angulo en relación al forward o el frente de nuestro actor donde forward es 0 y sin importar si esta a la derecha o a la izquierda si el angulo del enemigo es menor a FOV quiere decir que esta dentro del campo visual. Podemos incluso verificar un segundo angulo para saber si esta en angulo de tiro y así accionar el disparador en el caso de que sea un shooter 1if (enemy.angle < FOVForFire)Otro tema importante es el performance del componente, como pudieron observar para llegar a este punto debimos de hacer unas cuantas comprobaciones y estas comprobaciones deben ser efectuadas a cada instante por ello en el componente pudieron notar que estaba en la función Update ejecutándose en cada frame, pero esto no es lo mas idóneo ya que esto significaría un uso cuantioso por el numero de comprobaciones y constante por lo que lo mejor seria ejecutarla en una corutina o coroutine para que puedan aplicar bien este concepto les recomiendo que pasen por el articulo Entendiendo las Coroutines (Co-Rutinas) donde explico mejor este tema. Como les dije al comienzo no toque todos los aspectos de la inteligencia artificial ya que seria muy extenso el articulo, pero espero haberles dado una base o una idea de como armar su propia clase, tratare de en futuros artículos profundizar mas en algunos puntos de la AI por separado, por ejemplo, que el actor persiga al enemigo, tocare otros temas acerca de la navegación por navmesh, y otros comportamientos que pudieran presentarse en un juego. Nuevamente espero que le saquen provecho e inclusive puedan mejorar este tema con sus comentarios y sugerencias, lamento haberles alargado tanto la lectura pero como un iceberg esto es solo la punta. Saludos! fuente: https://eleazarcelis.wordpress.com/2015/10/10/creando-una-clase-basica-de-inteligencia-artificial-ai-en-unity/
  3. Quizás ya te ha pasado antes que al hacer algún tipo de cambio general en el código de tus scripts, bien sea cambiar el namespace o como en mi caso en particular que traslade mis clases del Assets folder a un proyecto aparte para importarlos como plugins, luego me encontré que los prefabs y demás gameobjects que tenia en escena todos perdieron la referencia al script. Si esto te pasa con un proyecto recién creado no es problema, solo un poco de trabajo adicional. Pero cuando tienes un proyecto avanzado sientes que la sangre te hierve en las venas. Tranquilo… no todo esta perdido! He encontrado este plugin en internet, francamente no sé el origen ni el autor, pero si alguno lo conoce por favor deje su nombre en este post para darle el debido credito por ello les dejo el link mas abajo. Solo coloquen el contenido del zip dentro de la carpeta Assets/Editor https://mega.nz/#!7h5yiIyL!AbsWdGxePPheHc_YT02l7tKsXiiBbL6SHBOJ970hSns El funcionamiento es bastante sencillo, pero cualquier duda comenten y con gusto les ayudare. fuente: https://eleazarcelis.wordpress.com/
  4. exacto, en unity puedes usar métodos asíncronos pero de forma implícita, es decir Application.LoadLevelAsync en efecto carga en un subproceso aparte el nivel pero no devuelve ningún Task por ello no se puede usar el keyword Await, ni se puede crear un Task, ni tampoco establecer un método como async.
  5. Microsoft desde la versión 7 de Windows soporta multithreading real sin ninguna segmentación de tiempo, el procesamiento es totalmente paralelo y tanto en C++ como C#, VB .NET y J# se puede correr tareas en diferentes "sub-procesos" corriendo totalmente en paralelo pero el proceso en si, es el ensamblado de la aplicación no tendría sentido tener varios procesos en una aplicación, pero si desde una aplicación ejecutas otro ensamblado bien sea que este en el mismo proyecto u otro de igual forma puedes correrlo en otro subproceso y totalmente en paralelo
  6. a nivel de procesamiento de datos en teoría un procesador multi-nucleo debería ser capaz de ejecutar paralelamente sin ninguna segmentación de tiempo*. y fuera de unity cualquier aplicación puede correr tareas "paralelamente" en otros hilos, inclusive Unity implementa algunas clases que son totalmente asíncronas. y si colocas un task(operación asíncrona) dentro de un bucle infinito (update) y dicho task tiene a su vez en su cuerpo un bucle infinito(el bucle que planteas) de igual forma veras la aplicación congelarse aunque esta este en otro "subproceso" y usando una operación asíncrona, no es una prueba fiable de que corren o no en otro "hilo". pero si te fijas en las imágenes se puede ver en la ventana de subprocesos de visual studio que los métodos Update y CoRutina corren en el mismo subproceso lo que prueba que efectivamente no corren paralelamente, inclusive en una prueba mas afondo pude verificar que se ejecutan en el mismo orden en que son llamadas por StartCoroutine, por eso en el ejemplo que coloque la ejecución de la corutina se ejecutara antes que el método Update *Nota: https://en.wikipedia.org/wiki/Thread_(computing)
  7. Que son las Co-Rutinas? Las Co-Rutinas (Coroutines) son métodos que tienen la capacidad de pausarse y reiniciarse exactamente donde se quedo en el frame anterior. Para entenderlo mejor, consideremos el siguiente ejemplo de la documentación oficial : 1234567void Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; c.a = f; renderer.material.color = c; }}Si quisiéramos hacer una función como la anterior y la colocamos en el Update no tendría ningún efecto fade debido a que la funcion se ejecutara toda en el mismo frame y por ello no se renderizara los estados intermedios entre 1 y 0. En cambio si la colocáramos dentro de una Co-Rutina de la siguiente manera: 12345678IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; c.a = f; renderer.material.color = c; yield return null; }}La función se pausara en la linea donde esta el yield y el bucle for continuara en el siguiente frame sin perder el valor de la variable f de esta manera en cada frame se renderizara el cambio entre el valor de c.a y c.a -0.1. Adicionalmente las Co-Rutinas se pueden pausar cada n segundos con la clase WaitForSeconds como vemos en el siguiente ejemplo: 12345678IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; c.a = f; renderer.material.color = c; yield return new WaitForSeconds(.1f); }}Esto es de suma importancia ya que podemos controlar la periodicidad del código de manera que no se ejecute en cada frame como la función Update, sino cada cierto tiempo. Para entender la importancia de este punto ejemplifiquemos el asunto. Supongamos que estamos creando una clase de AI (inteligencia artificial) y estamos diseñando la característica de Percepción de nuestro actor, si queremos que la clase este constantemente checando la posición de un enemigo, para calcular la distancia del enemigo, o si esta en rango y campo de visión o para verificar con un raycast si esta visible o no, esto podría significar un gasto cuantioso de recursos y podría afectar el rendimiento del juego. 1234567891011function Percepcion(){ for (int i = 0; i < enemies.Length; i++) { // implementacion aqui.... }} void Update(){ Percepcion();}Ahora bien, si usamos una Co-Rutina 123456IEnumerator Percepcion() { for(;;) { // implementacion aqui... yield return new WaitForSeconds(.5f); }}la implementación se ejecutara cada medio segundo y no en cada frame lo que representa una disminución significativa en el uso constante de recursos, e impactara de manera positiva en el performance del juego. Las Co-Rutinas se ejecutan en paralelo? Esta es una cuestión que siempre se da por sentado (inclusive yo mismo), e investigando un poco sobre este asunto me he percatado que realmente Unity no menciona explicitamente que sea de esa manera, así que me dispuse a hacer unas pruebas para verificar esto. 12345678910111213141516171819public class PruebaCoRutinas : MonoBehaviour { void Start () { StartCoroutine(CoRutina()); } void Update () { Debug.Log("Iniciando Update " + Time.frameCount); } IEnumerator CoRutina() { for (;;) { Debug.Log("Desde Corutina " + Time.frameCount); yield return new WaitForSeconds(1f); } }}Si ejecutamos el codigo anterior y abrimos la ventana de subprocesos de Visual Studio como se ve en las siguientes imagenes, notaremos inmediatamente que se ejecutan en el mismo subproceso, lo que prueba que la ejecución de una Co-Rutina en realidad no corre en paralelo. Espero les haya sido útil el articulo, siéntanse libres de comentar para alguna corrección o sugerencia. Fuentes: Unity Oficial Doc, Mi Blog
  8. la cuestión es que es una barra compuesta de 3 sprites, si modifico fill el sprite del borde derecho se quedara en su sitio quedando un espacio vacio entre él y el rectangulo central. mas bien lo que hice fue colocar el rectángulo central y el sprite derecho (el que tiene forma de triangulo) dentro del RecTransform que voy a controlar con async.progress, y el sprite izquierdo se queda estático en una posición negativa con respecto a su parent (progress_container), así progress puede tener un anclaje de todos los lados al igual que progress_container, esto me brinda flexibilidad a la hora de cambiar de resolución. pero si fuera una barra con un simple rectángulo si seria lo mejor como dices tu, gracias por tu observación.
  9. Saludos colegas! hoy quiero compartirles un método para crear un pre-loader de niveles en Unity, es bastante sencillo. El método que vamos a usar es Application.LoadLevelAsync(int|string) básicamente este método lo que hace es cargar el nivel en segundo plano lo que permite ir mostrando un progreso con su propiedad “progress”. Hay que tener en cuenta que la propiedad “progress” por alguna razón que desconozco solo va de 0.0f a 0.9f por lo que debemos multiplicar su valor por 100 si queremos mostrar el porcentaje, esto también implica que no podemos usarlo en algún condicional para saber si el nivel se cargo completamente o no, esto es algo que Unity debe solucionar ya que nunca llegara a 1f (o 100 si lo estas multiplicando), para ello deberemos usar la propiedad “isDone” cuyo valor sera false mientras se este cargando el nivel y true al completarse la carga. También es importante tener presente que una vez se cargue completamente el nivel, dicho nivel se activara automáticamente a menos que se le indique a la propiedad “allowSceneActivation” que no, esto es bastante útil si queremos mostrar o hacer “algo” antes de que se active el nivel, activar un efecto en la cámara, etc… Acá les comparto el código fuente de una clase que ya yo he creado: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859using UnityEngine;using UnityEngine.UI; namespace EMGame{ public class PreLoaderLevel : MonoBehaviour { public RectTransform progressBar; public Text textComponent; public bool immediateActivateScene = false; protected AsyncOperation async; protected float progressPercent; public virtual void Start() { // obtenemos de la escena anterior el index de la siguiente a cargar int nex_level = PlayerPrefs.GetInt("next_level"); // si no tiene valor hubo un error y devuelve a menu principal if (!PlayerPrefs.HasKey("next_level")) { nex_level = 0; } else { // se resetea el dato persistente para evitar errores PlayerPrefs.DeleteKey("next_level"); PlayerPrefs.Save(); } // se inicia la carca asincrona del nivel async = Application.LoadLevelAsync(nex_level); // se establece si se activara la escena inmediatamente async.allowSceneActivation = immediateActivateScene; } public virtual void Update() { if (async != null) { progressPercent = async.progress * 100; } if (textComponent) { textComponent.text = "Progress " + ((int)progressPercent).ToString() + " %"; } if (progressBar) { // aqui cada implementacion puede ser diferente float parentWidth = (progressBar.parent as RectTransform).rect.width; Vector2 progressBarSizeDelta = progressBar.sizeDelta; // ValueFromPercent calcula el valor de un porcentaje en base a su maximo valor x = (maxFactor / 100) * porentaje progressBarSizeDelta.x = -(parentWidth - progressPercent.ValueFromPercent(parentWidth)); progressBar.sizeDelta = progressBarSizeDelta; } } }}Como quizás notaron mi implementación esta basada en la siguiente jerarquía de objetos. Donde el área amarilla contiene las 3 partes de la barra de progreso el borde izquierdo, la pieza central y el borde derecho, y esto es así para evitar que la imagen se distorsionesizeDelta ya que su anclaje es de las cuatro esquinas y los cuatro bordes, no los complicare mucho con esto pero si quieren que su barra tenga alguna forma consideren “picar” su sprite de la siguiente forma: De esta manera no se pierde el efecto glow ni se distorsiona ya que a la imagen izquierda y a la derecha nunca se le modifica el “sizeDelta”. Espero que les sea de utilidad, y para cualquier duda o sugerencia pueden comentar fuente: https://eleazarcelis.wordpress.com/2015/09/15/desarrollando-un-pre-loader-de-niveles-con-barra-que-muestre-el-progreso-unity/
  10. fíjate que no del todo, como mencione el dropdown de unity no permite asignar un objeto subyacente a cada item, dado que esto era lo que yo necesitaba para asignar un KeyCode, o un objeto Resolution directamente al item para después obtenerlo, opte por terminar mi control, y funciona como lo esperaba. ahora puedo hacer algo como esto: dropdownlist.OnChange.AddListener(ResolutionSelected); ... void ResolutionSelected(DropDownListItem item) { Resolution resolution = (Resolution)item.value; Screen.SetResolution(resolution.width, resolution.height, true); } algo que con el de unity tendria que hacer un switch o castear el nombre del item
  11. Desde hace una semana he estado diseñando un menú de opciones para mi juego, como sabemos antes de la llegada de Unity 5.2 no existía un control tipo Dropdown entre el UI de Unity de manera que me había dedicado a diseñarlo. Luego de que el progreso de mi control estaba a un 80% Unity saca su versión 5.2 con el control así que me había apartado un poco del proyecto del mi control. Una noche decidí anexar el control Dropdown de Unity a mi proyecto y el control se comporto excelente, el control realmente hace lo que uno pudiera esperar de un Dropdown control, pero hay una limitación en el control de unity. Ahora imaginen este escenario: estas desarrollando un menú de opciones para tu juego y quieres que haya una opción para configurar los controles de tu jugador, para lograr esto deberemos ser capaz de pasarle un objeto de tipo KeyCode al evento controlador, por ejemplo: Input.GetKeyDown(KeyCode.E) es decir el cada item del dropdown debería ser capaz de almacenar cada KeyCode (KeyCode.A, KeyCode.B, etc…) para pasarselo directamente a Input.GetKeyDown, pero la limitante es que el control de Unity solo permite asignar dos propiedades “texto” y “sprite”, aunque si bien es cierto que pudiéramos hacer algún cast del string “texto” al enum esto se complicaría si el objeto que deseáramos asignar no es enum sino una clase mas compleja. Por esto decidí retomar mi diseño original de un componente Dropdown, el cual si implementa una propiedad llamada “value” de tipo object a la cual se le puede asignar cualquier tipo de valores. Para el ejemplo que les comente del listado con los KeyCodes la implementacion seria la siguiente: DropDownList dpComponent = dropdown.GetComponent<DropDownList>(); dpComponent.source = Enum.GetValues(typeof(KeyCode)). Cast<KeyCode>().Select(x => new DropDownListItem() { id = x.ToString(), text = x.ToString().CamelBreakup(), value = x, selected = x==KeyCode.E }).ToList(); La propiedad “source” del DropDownList permite definir el listado que se mostrara, cada item es un objeto de tipo DropDownListItem Les dejo el código fuente a continuación: para preguntas o sugerencias no se olviden comentar, espero les sea de utilidad. fuente: https://eleazarcelis.wordpress.com/2015/09/13/control-dropdownlist-para-unity/
UnitySpain © Todos los derechos reservados 2020
×
×
  • Create New...