15. ANEXO # 5 GAMEPLAYOBJECT A continuación se muestra el constructor de la clase GameplayObject public GameplayObject(Camera camera, int rows, int columns, GraphicsDevice dispositivo, bool invertir = false) _camera = camera; Parallax = Vector2.One; Rows = rows; Columns = columns; totalframes = Rows * Columns; device = dispositivo; Zoom = 1.0f; this.invertir = invertir; En el momento de la construcción del objeto GameplayObject es obligatorio inicializar las siguientes 2 propiedades. La propiedad Position la cual asigna al vector position las coordenadas X, Y de la posición del sprite correspondiente public Vector2 position = Vector2.Zero; public Vector2 Position get return position; set position = value; La propiedad Texture la cual asigna a la variable texture de tipo Texture2D la textura (atlas de textura) cargada gracias a un objeto ContentManager. Texture2D texture; public Texture2D Texture get return texture; set texture = value; //Aquí se asigna la propiedad Texturas. if (invertir) Texturas = new Texture2D [totalframes * 2]; else Texturas = new Texture2D [totalframes]; //Calcula la matriz global. (Global) = (Object) * (World) * (Camera) CalculateMatrix ();
En la construcción del personaje (objeto Character) que hereda de GameplayObject se aprecia cómo se inicializan estas 2 propiedades (junto con otras propiedades que no son necesarias de inicializar o asignar en todos los casos) como se muestra a continuación: _character = new Character (_camera, 1, 5, device, Content) Position = new Vector2 (50.0f, (device.viewport.height / 2) + 4), StartY = (device.viewport.height / 2) + 104, Texture = Content.Load<Texture2D> ("NivelUno/Imagenes/Cusumbo"), Speed = 400.0f ; Existen otras propiedades las cuales simplemente se tienen en la clase pero nunca se usan debido a que en el momento de creación de la clase se presumió su necesidad pero nunca realmente llegaron a necesitarse y otras que son usadas por algunos objetos y por otros no. private float _zoom; public float Zoom get return _zoom; set _zoom = value; protected Vector2 velocity = Vector2.Zero; public Vector2 Velocity get return velocity; set velocity = value; protected Vector2 acceleration = Vector2.Zero; public Vector2 Acceleration get return acceleration; set acceleration = value; protected float rotation = 0.0f; public float Rotation get return rotation; set rotation = value; protected float speed = 0.0f; public float Speed get return speed; set speed = value;
string name; public string Name get return name; set name = value; Se utiliza la propiedad llamada TextureData la cual asigna una lista de arreglos de colores a la variable texturedata (tipo List<Color []>) o devuelve la lista de arreglos de colores texturedata. En cada arreglo de colores (Color []) se guardan los colores de una textura del atlas de textura y si el objeto GameplayObject tiene la variable invertir en true también se tiene un arreglo de colores por cada una de las texturas invertidas, si por ejemplo un objeto GameplayObject presenta un atlas de textura con 5 texturas y la variable invertir esta en true, en total se tendrán 10 texturas y una lista con 10 arreglos de colores, un arreglo por cada textura. List<Color []> texturedata; public List<Color[]> TextureData get return texturedata; set //Esta propiedad fue asignada desde la propiedad Texturas. texturedata = value; La función que me permite invertir una textura es decir rotarla 180 grados con respecto a Y (yaw) es la siguiente: //Esta función recibe como parámetros el ancho, el alto y el arreglo de colores de la textura a //invertir. public Color[] invertirtextura(int anchounatextura, int altounatextura, Color[] texturedataelem) Color [] texturedataeleminver = new Color [anchounatextura * AltoUnaTextura]; for (int x = 0, i = anchounatextura - 1; x < anchounatextura; x++, i--) for (int y = 0; y < altounatextura; y++) texturedataeleminver [x + y * anchounatextura] = texturedataelem [i + y * anchounatextura]; return texturedataeleminver;
La propiedad Texturas se inicializa dentro de la propiedad Texture la cual recibe el atlas de textura (un atlas de textura de 1 fila por 1 columna significa que la textura global o atlas de textura solo contiene una textura). La propiedad Texturas permite inicializar el arreglo de texturas (texturas) el cual también puede ser obtenido, además permite inicializar la lista de arreglos de colores donde cada elemento de esta lista es un arreglo de colores de cada una de las texturas. Si invertir esta en true se crearan las texturas invertidas de las texturas originales gracias a la función invertirtextura y se insertaran al final del arreglo de texturas (texturas), asimismo al final de la lista de arreglos de colores se anexaran los arreglos de colores de las texturas que se han invertido. Esto se hace para cada una de las texturas del atlas de textura, de ahí que la propiedad Texturas haga uso de un ciclo for el cual contiene tantas iteraciones como frames existan (totalframes) en el atlas de textura, la variable entera i es la variable que itera en este ciclo. Columns es el número de columnas del atlas de textura. Para obtener la fila del atlas de textura se hace: row = (int)((float)i / (float)columns); Para obtener la columna del atlas de textura se hace: column = i % Columns; De esta manera el rectángulo que envuelve un frame del atlas de textura está dado por: Rectangle sourcerectangle = new Rectangle (AnchoUnaTextura * column, AltoUnaTextura * row, AnchoUnaTextura, AltoUnaTextura); Donde el primer y segundo parámetro son las coordenadas X, Y respectivamente de la esquina superior izquierda del rectángulo dentro del atlas de textura y el tercer y cuarto parámetro son el ancho y alto respectivamente del rectángulo dentro del atlas de textura. El rectángulo cubre el área de un frame o textura dentro del atlas de textura. 15.1 Propiedad Texturas El siguiente código pertenece a la propiedad Texturas: Texture2D[] texturas; public Texture2D[] Texturas
get return texturas; set //Esta propiedad fue asignada desde la propiedad Texture. texturas = value; //Aquí se asigna la propiedad TextureData, y es donde se guardan los arreglos //de colores de las texturas tanto normales como invertidos. if (invertir) TextureData = new List<Color []> (totalframes * 2); else TextureData = new List<Color []> (totalframes); //Lista donde se guardaran provisionalmente los arreglos de colores invertidos de //las texturas originales. List<Color []> TextureDataTemporal = new List<Color []> (totalframes); AnchoUnaTextura = Texture.Width / Columns; AltoUnaTextura = Texture.Height / Rows; int row = 0; int column = 0; for (int i = 0; i < totalframes; i++) row = (int) ((float)i / (float)columns); column = i % Columns; Rectangle sourcerectangle = new Rectangle (AnchoUnaTextura * column, AltoUnaTextura * row, AnchoUnaTextura, AltoUnaTextura); //Textura normal. Texture2D textura; //Arreglo de los colores de la textura actual. Color [] texturedataelem; texturedataelem = new Color[AnchoUnaTextura * AltoUnaTextura]; Texture.GetData<Color> (0, sourcerectangle, texturedataelem, 0, texturedataelem.length); //Creo una nueva textura. textura = new Texture2D(device, sourcerectangle.width, sourcerectangle.height, false, SurfaceFormat.Color); //Asigno los datos (colores) a la nueva textura normal. textura.setdata (texturedataelem); //Asigno una textura normal al arreglo de texturas. Texturas[i] = textura; //Adiciono un arreglo de colores normal a la lista de arreglos de colores. TextureData.Add (texturedataelem); if (invertir) //Textura invertida. Texture2D texturainver; //Arreglo de los colores de la textura actual invertida. Color [] texturedataeleminver; //La función invertirtextura me devuelve una lista de colores, //la cual me permite invertir la textura. texturedataeleminver = invertirtextura(anchounatextura, AltoUnaTextura, texturedataelem); //Creo una nueva textura.
texturainver = new Texture2D(device, sourcerectangle.width, sourcerectangle.height, false, SurfaceFormat.Color); //Asigno los datos (colores) a la nueva textura invertida con //respecto a la original. texturainver.setdata (texturedataeleminver); //Asigno una textura invertida al arreglo de texturas. Texturas [i + totalframes] = texturainver; //Adiciono un arreglo de colores invertido a la lista temporal de //arreglos de colores invertidos. TextureDataTemporal.Add (texturedataeleminver); //Adiciona al final de la lista de arreglos de colores, la lista temporal de arreglos de //colores invertidos. if (invertir) TextureData.AddRange (TextureDataTemporal); Se utiliza la propiedad Colision (se usa en el tercer y cuarto nivel) para indicar cuando un sprite de un objeto GameplayObject presenta colision con un sprite de otro objeto GameplayObject. Si el sprite presenta colisión la variable booleana colision se pone en true, false en caso contrario. bool colision; public bool Colision get return colision; set colision = value; Se hace uso de la propiedad Rectangle la cual permite obtener una instancia de la estructura Rectangle de nombre rectangle. Rectangle rectangle; public Rectangle Rectangle get return rectangle; En la función CalculateBoundingRectangle () se inicializa la variable rectangle con la delimitación correcta de una textura dentro del atlas de textura (todas las texturas del atlas de textura presentan el mismo ancho y el mismo alto), que se dibujara en pantalla; aunque el rectángulo (rectangle)
podría no solapar exactamente el rectángulo que encierra el sprite o textura a dibujarse. Gracias a la matriz global (Transform = WorldMatrix () * ViewMatrix (Parallax)) se puede obtener un rectángulo que encierre al rectángulo donde se dibujara (coordenadas de pantalla) el sprite del objeto correspondiente. La siguiente es la función que permite construir el rectángulo delimitador del sprite a dibujarse en coordenadas de pantalla. protected void CalculateBoundingRectangle() if (texture!= null) rectangle = new Rectangle(0, 0, (int)texture.width / Columns, (int)texture.height / Rows); Vector2 lefttop = Vector2.Transform (new Vector2 (rectangle.left, rectangle.top), Transform); Vector2 righttop = Vector2.Transform (new Vector2 (rectangle.right, rectangle.top), Transform); Vector2 leftbottom = Vector2.Transform (new Vector2 (rectangle.left, rectangle.bottom), Transform); Vector2 rightbottom = Vector2.Transform (new Vector2 (rectangle.right, rectangle.bottom), Transform); Vector2 min = Vector2.Min (Vector2.Min (lefttop, righttop), Vector2.Min (leftbottom, rightbottom)); Vector2 max = Vector2.Max (Vector2.Max (lefttop, righttop), Vector2.Max (leftbottom, rightbottom)); rectangle = new Rectangle((int)min.X, (int)min.y, (int)(max.x - min.x), (int)(max.y - min.y)); En la imagen se aprecia cómo queda el rectángulo delimitador (Ver Imagen 56 Rectangulo Delimitador) el cual está más subrayado, y como podría quedar un sprite representado por el rectángulo de adentro de acuerdo a una matriz de transformación que lo ha modificado. Imagen 56 Rectangulo Delimitador En XNA la matriz global de transformación es definida de la siguiente forma:
(Global) = (Object) * (World) * (Camera) * (Perspective). Donde Object se refiere a las coordenadas propias de los pixeles dentro de los sprites, World es la matriz de mundo, Camera se refiere a la matriz de vista y Perspective se refiere a la matriz de proyección que por defecto es una matriz ortográfica la cual mapea los objetos (sprites) directamente sobre la pantalla sin afectar su tamaño relativo, caso contrario de cuando se usa una matriz de proyección en perspectiva la cual hace que los objetos que estén más lejos se vean más pequeños. Se utiliza la matriz de proyección que viene por defecto la cual es ortográfica y está implícita en las operaciones de matrices. La función CalculateMatrix () utiliza la propiedad Transform para asignar y obtener la matriz de transformación global que se usara al validar las colisiones per-pixel. public Matrix Transform get; set; La matriz de mundo sitúa al sprite en el mundo. Básicamente lo que hacen las transformaciones que se utilizaron para la construcción de esta matriz fue primero situar la mitad del sprite correspondiente en la esquina superior izquierda del rectángulo de pantalla que funciona como origen del mundo, luego se escala, entonces se rota y finalmente se traslada a la posición en la que se desea ubicarlo. public virtual Matrix WorldMatrix() return Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) * Matrix.CreateScale (Zoom, Zoom, 1.0f) * Matrix.CreateRotationZ (Rotation) * Matrix.CreateTranslation(new Vector3(Position, 0.0f)); En la función CalculateMatrix () se genera la matriz de transformación (WorldMatrix () * _camera.getviewmatrix (Parallax)) la cual es asignada a la propiedad Transform. protected void CalculateMatrix() // (Global) = (Object) * (World) * (Camera) // WorldMatrix () es la función que devuelve la matriz de mundo y // _camera.getviewmatrix (Parallax) es la instancia (_camera) de //la clase Camera que llama al método GetViewMatrix el cual //devuelve la matriz de vista.
Transform = WorldMatrix () * _camera.getviewmatrix (Parallax); La función virtual update de la clase GameplayObject se llama en cada ciclo de video en la función update de cada clase principal de los diferentes niveles para actualizar los respectivos objetos GameplayObject. En caso de que el objeto herede de GameplayObject se llamara su correspondiente método update y si maneja colisiones tendrá de todas maneras que llamar a las funciones CalculateMatrix () y CalculateBoundingRectangle (). public virtual void Update(GameTime gametime) CalculateMatrix (); CalculateBoundingRectangle (); La función virtual Draw de la clase GameplayObject se llama en cada ciclo de video en la función Draw de cada clase principal de los diferentes niveles para actualizar la posición de los respectivos sprites de los objetos GameplayObject. En caso de que el objeto herede de GameplayObject se llamara el método Draw sobrescrito (override) de la clase derivada si este existe (en la clase derivada) sino se llamara al de la clase base (GameplayObject). La función virtual Draw permite dibujar el sprite del objeto correspondiente posicionándolo de acuerdo a la traslación y dirección que le dictamine la matriz de transformación global (WorldMatrix () * _camera.getviewmatrix (Parallax)). public virtual void Draw(SpriteBatch spritebatch) spritebatch.begin (SpriteSortMode.Deferred, null, null, null, null, null, WorldMatrix () * _camera.getviewmatrix (Parallax)); if (Texturas[currentFrame]!= null) spritebatch.draw (Texturas [currentframe], Vector2.Zero, Color.White); spritebatch.end ();