Noticias:

Antena 3 estrena Los Simpson, una serie fresca e innovadora, tras su paso por TVE.

Menú Principal

Curso de Programación Lúdica. Actualmente: Tetris funcionando

Iniciado por Bill, 13 de Mayo de 2009, 15:08

0 Miembros y 1 Visitante están viendo este tema.

danyof

Creo que hablo en nombre de todos/as cuando digo....

Gambit vuelve!! pero que sea antes de navidad!! :)

neoprogram

Es verdad, que ahora que en cierta clase me dicen que no se C ni java, me vendría bien aprender C#...  :llori:




たとえばここには愛すべき声があってあたしはそれすた守れなくて

neoprogram

Cita de: neoprogram en 12 de Noviembre de 2009, 20:34
Es verdad, que ahora que en cierta clase me dicen que no se C ni java, me vendría bien aprender C#...  :llori:




たとえばここには愛すべき声があってあたしはそれすた守れなくて


Bill

Ah, perdón... lo siento. Mi nivel de informática no es suficiente para estos menesteres. Eso es algo que he aprendido de grandes maestros como El Brujo, por tanto en una "charla" con él en otro hilo prometí que no volvería por la sección de informática. Dado que él es tan bueno y tan listo, veo conveniente que sea él quien prosiga el curso, dado que a su lado no soy más que un mero gusano. Las lecciones las he seguido haciendo, pero existiendo por aquí gente tan pro como él no veo adecuado el lugar, en cualquier caso si a alguien le interesa el curso dado por un inepto en desarrollo informática como yo, que me lo haga saber y lo continúo en un foro alternativo que no sea este.

Un saludo: Gambit.

Superjorge

Cita de: Gambit en 27 de Noviembre de 2009, 16:42
Ah, perdón... lo siento. Mi nivel de informática no es suficiente para estos menesteres. Eso es algo que he aprendido de grandes maestros como El Brujo, por tanto en una "charla" con él en otro hilo prometí que no volvería por la sección de informática. Dado que él es tan bueno y tan listo, veo conveniente que sea él quien prosiga el curso, dado que a su lado no soy más que un mero gusano. Las lecciones las he seguido haciendo, pero existiendo por aquí gente tan pro como él no veo adecuado el lugar, en cualquier caso si a alguien le interesa el curso dado por un inepto en desarrollo informática como yo, que me lo haga saber y lo continúo en un foro alternativo que no sea este.

Un saludo: Gambit.

Vamos hombre no me jodas Peluche
Sorry but you are not allowed to view spoiler contents.

Genki

Peluche, sé racional y piensa... eres mayorcito para pasar de lo que te diga El Brujo...

Sorry but you are not allowed to view spoiler contents.

danyof

Eso mismo pienso yo. No se que movidas habreis tenido... pero eso de ahora no hago el curso porque me enfado me suena a niño de parbulario, sin animo de ofender por supuesto!

A mi me gustaria que el curso continuara, fuese aqui o en algun otro sitio.

neoprogram

Cita de: danyof en 29 de Noviembre de 2009, 18:42
A mi me gustaria que el curso continuara, fuese aqui o en algun otro sitio.

Si... además, echo de menos la voz de enseñar en los cursos de Peluche  *.*




たとえばここには愛すべき声があってあたしはそれすた守れなくて

Bill

3. Scroll horizontal 2D multicapa

A ver si se entera Penguin, que no me quedé muy convencido  :gñe:

Vamos con algo fácil de digerir y muy utilizado en juegos 2D. En este caso se trata del scroll horizontal 2D multicapa, es decir, que exista una localización "larga" en la que nos podemos desplazar en horizontal y que existan varias capas en el fondo que se desplacen a velocidades diferentes para dar la sensación de profundidad.

Lo primero que hay que entender es el concepto de profundidad y su relación con la transparencia. Las capas serán gráficos independientes (en nuestro caso en png) que contendrán información de transparencia. De no utilizar un formato de gráfico con transparencias, existen opciones alternativas como decidir un color como transparente el cual no se pintará o utilizar otro gráfico como capa alpha que llevará información binaria de lo que se pintará y lo que no. En nuestro caso, al utilizar png como formato gráfico, la transparencia va incluída en el gráfico así que no necesitamos nada más. ¿En qué orden hay que pintar las capas? Siempre las más alejadas se pintan primero, para que las más cercanas se superpongan. Sencillo y para todos los públicos.

Además hay que entender lo que es el punto de vista, lo que sería la zona de proyección. Vamos a verlo con un ejemplo gráfico:



Vamos a explicarlo. Por una parte tenemos el escenario, que mide 400px de altura y 4000px de ancho. Por otra parte tenemos el punto de vista, que es lo que se pinta en pantalla, y que en este caso tendrá la misma altura que el escenario (dado que es un scroll horizontal puro, si no tuviesen la misma altura existiría también scroll vertical) y de ancho 500px. Entonces en cada momento se pintará solamente un trozo del escenario. El punto inicial es en el que x=0, con lo que estamos mostrando el trozo desde la x=0 hasta la x=499 del escenario. El punto máximo es en el que x=3500 dado que estaremos mostrando el trozo desde la x=3500 hasta la x=3999 del escenario, el cual es de 4000px de ancho, con lo cual si nos fuésemos un poco más hacia la derecha se mostraría vacío.

Ahora mismo sabemos (funcionalmente) cómo mostrar un trozo de un escenario estático. ¿Cómo se hace para tener varias capas y que den sensación de profundidad?. Vamos a pensar en 4 capas:
- La más cercana es el suelo
- Luego vienen unas montañas
- Luego unas nubes
- Por último unas nubes más alejadas y el cielo.

Todas las capas deben tener la misma altura (dado que no tenemos scroll vertical), pero... ¿deben tener el mismo ancho? Si todas tuviesen el mismo ancho no existiría sensación de profundidad, todas se moverían a la misma velocidad y a la par, así que habría sensación de movimiento horizontal pero con un fondo estático, que sería lo mismo que tener una única imagen con las 4 capas superpuestas. La capa que se debe mover a mayor velocidad es la más cercana al punto de vista, con lo cual el suelo, las montañas deben moverse un poco más lento pero más rápido que las nubes, y las nubes un poco más rápido que las nubes alejadas. Para conseguir este efecto las capas más alejadas tendrán un ancho menor.

Para nuestro ejemplo utilizaremos los siguientes gráficos:

Sorry but you are not allowed to view spoiler contents.


Así que tenemos:
- Suelo.png: 400x4000px
- Montana.png: 400x3000px
- Nubesfrente.png: 400x2000px
- Nubesfondo.png: 400x1500px

Ahora es cuando llega la formulación matemática: dada una posición x en la que estamos, ¿qué posición x tenemos en cada una de las capas? Con suelo.png lo tenemos solucionado porque es la capa más cercana, con lo cual es la de referencia. Para todas las demás se usará una regla de 3, pero ¡ojo! ¡No una regla de tres con la longitud total! Es decir, en el punto 0 todas las capas tendrán desplazamiento 0, pero el punto máximo no es 3999px sino 3500px, dado que de desplazarnos más a la derecha se verían vacíos. Entonces:

Dado un ancho refWidth de la capa de referencia, un ancho viewWidth del punto de vista, un ancho layerWidth de la capa que queremos calcular el desplazamiento, y una posición actual actualX, el cálculo del desplazamiento de la capa en cuestión será:

despX = actualX*(layerWidth-viewWidth)/(refWidth-viewWidth);

Esta fórmula, como podemos comprobar, para la capa de referencia siempre nos da que despX = actualX.

Programando el scroll con Visual Studio C# en WindowsForms

1. Vamos a crear el proyecto. Abrimos Visual Studio y seleccionamos Archivo -> Nuevo -> Proyecto. Seleccionaremos Aplicación de Windows Forms y pondremos el nombre del proyecto y solución (en nuestro caso, TestScroll2D). Y hacemos click en Aceptar



Esto lo que hace es crearnos el código inicial de la solución y el proyecto, además del formulario por defecto.

2. Hacemos click en el formulario que nos ha creado para ver la paleta de propiedades. En el caso de que no nos aparezca la ventana de propiedades, hacer click en Ver -> Ventana Propiedades. Aquí es dónde vemos las propiedades de nuestra ventana, que será lo que se muestre al usuario. Nos vamos a la propiedad Text y la cambiamos por Test Scroll 2D, y a la propiedad Size y la cambiamos por 500; 400. Esto hará que la ventana tenga las proporciones que le hemos marcado.



Si ahora pulsamos F5 (compilará y ejecutará el programa) veremos lo siguiente:



3. El programa se ha compilado en el path en el que tengamos el proyecto, y bin/debug. En ese mismo path, en el que veremos el ejecutable de nuestro programa al que podemos hacer doble click y ejecutarlo desde ahí para comprobar, copiamos las imágenes con los nombres correctos (ojo, no es montanau.png, sino montana.png, aseguraos de que los nombres son correctos).



4. Vamos a comenzar a programar. En el explorador de soluciones, dónde vemos Form1.cs, damos click con el botón derecho del ratón y elegimos Ver Código. Se nos abrirá una pestaña en la que veremos el código profabricado de nuestra aplicación. Lo primero que se ve son los using, que son las bibliotecas a las que se hacen referencia.



Por ahora vamos a dejar los using como están. Luego vemos la declaración del namespace, que es el nombre de la biblioteca en el que incluímos nuestro código. Y debajo la declaración de la clase Form1 como heredera de la clase Form y además indicando que es parcial (partial). ¿Qué significa esto de parcial? Pues que lo que vemos no es todo el código de la ventana, se nos está mostrando solamente parte del código para no liarnos. El resto del código está en Form1.Designer.cs, y contiene información visual de los componentes. Dentro de la clase vemos el constructor:

Citarpublic Form1()
        {
            InitializeComponent();
        }

Toda clase tiene un constructor, que es lo primero a lo que se invoca cuando se crea una instancia de dicha clase. Ya os iréis acostumbrado a los conceptos de clase e instancia con su uso.

5. Vamos a cargar las imágenes. Para ello tenemos la clase Bitmap. Toda clase se instancia con la palabra "new" y el constructor de la clase. Ponemos este código dentro de la clase Form1 y antes de su constructor:

Citarprivate Bitmap btmNubesFondo = new Bitmap("nubesfondo.png");
        private Bitmap btmNubesFrente = new Bitmap("nubesfrente.png");
        private Bitmap btmMontana = new Bitmap("montana.png");
        private Bitmap btmSuelo = new Bitmap("Suelo.png");

Analicemos una de ellas:
Sorry but you are not allowed to view spoiler contents.


La primera palabra, private, indica la visibilidad que tiene lo que estemos declarando, en este caso private indica que solamente será visible para la clase en la que está declarada. Otros tipos comunes de visibilidad son protected, que indica que lo puede ver la clase y sus descendientes, y public que indica que el acceso no está restringido. En nuestro caso son campos que solamente utilizará nuestra clase, con lo que el acceso privado es el recomendado.
La segunda palabra, Bitmap, es una clase que se encuentra en la biblioteca System.Drawing y que representa a una imagen.
La tercera palabra, en este caso btmNubesFondo, es el nombre que le damos a nuestro campo (un equivalente a las variables).
Luego tenemos un signo igual seguido de la palabra new que indica la creación de clase, y Bitmap("nubesfondo.png") que es el constructor de la clase en el que le indicamos que cargue el fichero "nubesfondo.png" alojado en el mismo sitio que el ejecutable que se genere.

6. Declaramos el rectángulo visible que se pinta en la pantalla, el rectángulo correspondiente a la capa de referencia completa y la X actual en la que nos encontramos con respecto a la capa de referencia:

Citarprivate Rectangle viewRect;
        private Rectangle referenceRect;
        private int actualX;

Como vemos, no los estamos inicializando. Eso lo hacemos en el constructor de la clase, en el cual ya tendremos todos los datos necesarios:

Citarpublic Form1()
        {
            InitializeComponent();
            DoubleBuffered = true;
            viewRect = new Rectangle(0, 0, Width, Height);
            referenceRect = new Rectangle(0, 0, btmSuelo.Width, btmSuelo.Height);
            actualX = 0;
        }

La primera sentencia, la del DoubleBuffered=true, es para indicarle a la ventana que a la hora de pintar tenga doble buffer, es decir, que no haya parpadeos gordos desagradables, sino que pinta en una segunda versión duplicada en la memoria de vídeo y cuando termina de pintar en esa versión es cuando lo pasa a la zona de memoria visible de pantalla.
Luego creamos el viewRect, nuestro rectángulo de vista, iniciando en el punto 0,0 de la ventana, y con ancho el ancho de la ventana (Width) y alto el alto de la ventana (Height). Así tendremos definido un rectángulo en el punto (0,0) con ancho 500 y altura 400 en nuestro caso.
Luego creamos el referenceRect, el rectángulo de la capa de referencia, iniciado en el punto 0,0 de la imagen de referencia y con su mismo ancho y alto.
Por último indicamos que estamos en la x=0.

Todavía nuestra aplicación no está haciendo nada adicional, simplemente carga los gráficos e inicializa cosas.

7. Vamos a declarar un método (una función de clase) que se encargue de pintar una capa calculando su desplazamiento con respecto a las fórmulas que vimos antes. A este método le llamaremos PaintLayer, y aquí está su implementación:

Citarprivate void PaintLayer(Graphics graphics, Bitmap layer)
        {
            int layerX = actualX * (layer.Width - viewRect.Width) / (referenceRect.Width - viewRect.Width);
            Rectangle layerRect = new Rectangle(layerX, 0, viewRect.Width, viewRect.Height);
            graphics.DrawImage(layer, viewRect, layerRect, GraphicsUnit.Pixel);
        }

Los parámetros que se pasan a este método son un graphics y un layer, graphics es dónde pintamos y layer es la imagen que vamos a pintar (la capa).
Como vemos, calculamos layerX con respecto a la fórmula que vimos antes, y definimos un rectángulo, que será el que se pintará de la capa en cuestión, con el desplazamiento en las x de layerX, ningún desplazamiento en el eje Y, y con el alto y ancho de la vista.
Al final se invoca al DrawImage del graphics que se le está pasando, que por ahora no hace falta que lo entendais, pero se le pasa una imagen a pintar, un rectángulo de destino, un rectángulo de origen de la imagen y el modo de pintado.

8. Muy bien, tenemos los gráficos cargados, las vistas inicializadas, tenemos un método para pintar una capa en concreto... ¿y dónde pintamos? Hemos visto propiedades, hemos visto métodos, nos quedan los eventos. Los eventos son métodos que se autoinvocan cuando sucede algo. En este caso queremos reaccionar al evento Paint del formulario. Para ello hacemos click en el formulario para que se nos muestren sus propiedades, y hacemos click en el icono del rayo, que es el de eventos, buscamos el Paint y le hacemos doble click. Eso nos creará un método asociado al evento.



Dentro del método que nos ha creado automáticamente tenemos que hacer que llame a nuestro método para pintar capas para cada capa, y que los pinte en la región de gráficos que se nos pasa al evento en sus argumentos de evento como e.Graphics:

Citarprivate void Form1_Paint(object sender, PaintEventArgs e)
        {
            PaintLayer(e.Graphics, btmNubesFondo);
            PaintLayer(e.Graphics, btmNubesFrente);
            PaintLayer(e.Graphics, btmMontana);
            PaintLayer(e.Graphics, btmSuelo);
        }

Ahora ya tenemos algo, cuando arrancamos la aplicación se llamará al evento Paint, con lo que se nos pintará lo que sería el punto de vista para la x = 0.



9. Vamos a añadir movimiento. Primeramente vamos a definir una función que dada una representación de una tecla, haga algo si dicha tecla se corresponde con la flecha derecha, algo si se corresponde con la flecha izquierda y además responda si se trataba de una de dichas teclas.

Citarprivate bool ProcessKeyDown(Keys keyCode)
        {
            switch (keyCode)
            {
                case Keys.Left:
                    {
                        if (actualX > 5)
                        {
                            actualX -= 5;
                            Invalidate();
                        }
                        return true;
                    }
                case Keys.Right:
                    {
                        if (actualX < referenceRect.Width - viewRect.Width-5)
                        {
                            actualX += 5;
                            Invalidate();
                        }
                        return true;
                    }
            }
            return false;
        }

Aquí vemos la introducción al Switch, un tipo de sentencia que nos permite comprobar los distintos valores que puede tomar un campo y ejecutar código diferente según el valor. En nuestro caso vamos a mover con incrementos de 5 píxeles, que aunque aquí está puesto a "fuego" debería ir en un campo o una constante externa definible. Por tanto podemos movernos a la izquierda siempre que existan 5 pixeles de holgura, y podremos movernos a la derecha siempre que no se exceda el máximo de visualización de la capa base según definimos antes (si la capa mide 4000px y el rectángulo de vista 500px, el máximo punto será el 3500). En caso de que sea una de dichas teclas, se devuelve true y se sale del método, en caso contrario se devuelve false.

Pero, ¿cómo interceptamos las teclas? De nuevo es un evento, en este caso el KeyDown del formulario.



En ella debemos invocar al método que hemos definido:

Citarprivate void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = ProcessKeyDown(e.KeyCode);
        }

Conclusión
Con esto queda terminada una aplicación que permite utilizar las teclas direccionales izquierda y derecha para movernos por el scroll, y en el que vemos la sensación de profundidad en base a 4 capas, quedando además suficientemente genérico como para que dichas capas puedan ser sustituidas por otras cualesquiera. Un ejemplo del scroll cuando x = 300:



Espero que haya quedado claro el concepto de scroll horizontal, en realidad el vertical funciona exactamente de la misma manera, y el mixto (scroll horizontal y vertical a la vez) también. Todo es entender el rectángulo de vista y las capas, y como la velocidad a las que las desplazamos no es la misma, y así con las transparencias se consigue el efecto deseado.

Subiría el código fuente y ejecutable y todo, pero no tengo acceso a megaupload.

Últimos mensajes