Anterior | Índice | Siguiente
Capítulo 9: Personajes caminando
Lenguaje: C#
Para: VS2005 / VS2008 / SharpDevelop 2.2
Por Dark-N: hernaldog@gmail.com
Visita mi Blog


http://darknromhacking.com


Objetivo: Se hará un piso con 3 personajes que caminen con sentido aleatorio, si chocan entre si, se detienen y buscan un nuevo rumbo. Además, está cayendo nieve ;)

Nota: Para la codificación de la nieve me basé en los ejemplos de SDL.Net

En los juegos de ROL o RPG, es muy conocido el personaje NPC (Non-player character) que es aquel que no es controlado por el usuario. En nuestro caso tenemos 3 NPC que caminan en un cuarto.

Recursos
  • Hoja de sprites: es una imagen png que tiene varios NPC adentro "dibujados" en distintas acciones, como corriendo o detenido.
  • Pasto verde: Será el piso del cuarto.
  • Árbol: El cuarto estará rodeado de árboles. Usaremos un sprite de árbol para representarlos a todos.
  • Fuente Arial: La fuente que quise usar para escribir mensajes en la pantalla.

    Para partir a codificar, lo primero es definir la clase NPC. Esta debe tener:
  • un id único que lo identifique del resto
  • una dirección de movimiento
  • un tiempo único, ya que los 3 NPc no se pueden mover todos a la vez, algunos son más lentos que otros
  • un objeto rectangulo que define las posiciones dentro de la hoja de sprites (una imagen png que tiene varios NPC adentro "dibujados" en distintas acciones, como corriendo o detenido)
  • un Intervalo de movimiento que define cuantos "ticks" debe moverse. A más ticks, más caminará
  • un tiempo restante de movimiento, que almacena cuantos "ticks" le quedan para moverse
  • otras variables únicas de cada NPC.

    public class NPC 
    {
    	public int id;
    	public AnimatedSprite sprite;
    	public Size tamano;
    	public Rectangle rectangulo;
    	public int direccion;
    	public bool moverPrimeraVez;
    	public int intervaloMovimiento;
    	public int tiempoRestante;
    	public Point centroInicial;   	
    	
    	public NPC(int id, Size t, Rectangle r, int intervalo, int tiempo, Point centroIni)
    	{
    		this.id = id;
    		this.tamano = t;
    		this.rectangulo = r; 
    		this.moverPrimeraVez = true;
    		this.intervaloMovimiento = intervalo;
    		this.tiempoRestante = tiempo;
    		this.centroInicial = centroIni;
    	}
    }

    Ahora en la carga inicial, para cada NPC se le debe indicar las posiciones de partida y el rectángulo dentro de la hoja de Sprites:

    private ArrayList listaNpc = new ArrayList();
    
    public void Run()
    {
    	NPC npc1;        
    	...
    	npc1 = new NPC(1, new Size(24,32),new Rectangle(0,0,72,132), 100, 100, new Point(50,50));
    	npc1.sprite = CargaNPC(npc1.tamano, npc1.rectangulo);
    	npc1.sprite.CurrentAnimation = "abajo";            
    	npc1.sprite.Center = npc1.centroInicial;
    	...
    	listaNpc.Add(npc1);
    	...
    	Events.Run();
    }
    
    
    AnimatedSprite CargaNPC(Size size, Rectangle rect)
    {        
    	AnimatedSprite ap = new AnimatedSprite();
    	AnimationCollection an1 = new AnimationCollection();
    
    	Surface surf_temp = new Surface(72,132).Convert(screen, true, false);
    	surf_temp.Blit(personajes, new Point(0,0), rect);            
    	...
    	SurfaceCollection walkUp = new SurfaceCollection();
    	walkUp.Add(surf_temp, size, 0);
    	...
    	an1.Add(walkUp);
    	ap.Animations.Add("arriba",an1);            
    	...
    	ap.TransparentColor = Color.Black;
    	ap.Transparent = true;	
    	
    	return ap;
    }
    

    Veamos en detalle para el caso del NPC1:
  • El Tamaño del sprite npc, en este caso es de 24 x 32 pixels
  • El rectángulo usado para leer el sprite completo del npc dentro de la hoja de sprites, en este caso se sacará de la posición 0 (x), 0 (y), 72 (ancho), 132 (largo). Esto quiere decir que dentro de esa posiciones, en el PNG está definido todas las acciones del npc, como subir, bajar, correr, etc.
  • intervalo de movimiento 100, es decir que se moverá en intervalos de 100 ticks, luego se queda quieto, luego otros 100 ticks, y asi.
  • tiempo restante 100, es decir que parte con 100 ticks, luego se quedará quieto. Se reinicia el contador y parte nuevamente.
  • el punto de partida en la pantalla es en la posición 50 (x), 50 (y).
  • Para pintar los personajes más tarde es muy cómodo usar una "lista" de npc, para esto usamos el ArrayList listaNPC.
  • El método "CargaNPC" se encarga de cargar las animaciones en un objeto "SurfaceCollection" leyendo el sprite

    Game-loop

    Ahora que sabes como se crea la clase, veamos el game-loop como debe ser para tengas una idea de que hay que renderear:

    //ciclo de juego (game-loop)
    private void Evento_Tick(object sender, TickEventArgs args)
    {        	      	
        screen.Fill(Color.Black);          
        pintaMensajes();
        pintaSuelo();
        pintaArboles();
        pintaNPCs();
        screen.Blit(copos);
        screen.Update();
    }
    


    Los primero es pintar los mensajes en pantalla, cosa sencilla:

    void pintaMensajes()
    {
    	TextSprite  textoSup = new TextSprite(msg_superior, fuente, Color.Yellow);
    	screen.Blit(textoSup,new Point(10,3)); 
    	...		
    }
    

    Para cargar los Árboles y el piso se hace:

    public void Run()
    {  
    	private Surface arbol;
    	private Surface suelo;
    	private Sprite spr_arbol;
    	...
    	arbol = new Surface("cap9_arbol.png").Convert(screen, true, false);
    	spr_arbol = new Sprite(arbol);
    	suelo = new Surface("cap9_pasto.PNG").Convert(screen, true, false); //16 x 16 pixels
    	...
    	Events.Run();
    }
    
    void pintaArboles()
    {          	 
    	for (int i = unidadPixel; i <= rectArboles.Height; i=i+unidadPixel)  //linea vertical izquierda
    		screen.Blit(spr_arbol, new Point( unidadPixel, i ));
    	
    	for (int i = unidadPixel; i <= rectArboles.Height; i=i+unidadPixel)  //linea vertical derecha
    		screen.Blit(spr_arbol, new Point( rectArboles.Width, i ));
    	
    	for (int i = unidadPixel; i <= rectArboles.Width; i=i+unidadPixel)  //linea horiz superior
    		screen.Blit(spr_arbol, new Point( i, unidadPixel ));
    	
    	for (int i = unidadPixel; i <= rectArboles.Width; i=i+unidadPixel)  //linea horiz inferior
    		screen.Blit(spr_arbol, new Point( i, rectArboles.Height ));
    }
    
    void pintaSuelo()
    {
    	for (int i = rectSuelo.X; i <= rectSuelo.Width; i=i+unidadPixel)
    		for (int j = rectSuelo.Y; j <= rectSuelo.Height; j=j+unidadPixel)
    			screen.Blit(suelo, new Point( i, j ));        	
    }
    

    Para pintar los NPC debemos leer el ArrayList "listaNpc" cargado antes. Aquí se le dan 2 lógicas:

  • Si el NPC choca con la pared se detiene y no sigue andando. Luego busca otra dirección aleatoria. Si vuelve a chocar con la pared, nuevamente busca, y así.
  • Si choca con otro personaje, ambos deben detenerse y buscar otra dirección aleatoria. Se muestra el mensaje superior "Intercepta A con B", donde A y B son los NPC que chocan.
    Imagen de Ejemplo.

    void pintaNPCs()
    {
    	foreach(NPC npc in listaNpc)
    	{     	                       	
    		...		
    		if (npc.direccion == 1) //arriba
    		{
    			if (npc.sprite.CurrentAnimation != "arriba")
    				npc.sprite.CurrentAnimation = "arriba";
    				
    			if (npc.sprite.Top > rectSuelo.Y)  
    			{	
    				int poc_anterior = npc.sprite.Y;
    				npc.sprite.Y = npc.sprite.Y - velocidad_mov;
    				
    				if (validaColisionFutura(npc) == true)
    					npc.sprite.Y = poc_anterior;
    				
    				npc.sprite.Animate = true;
    			}     		
    		}
    		...
    	
    		npc.tiempoRestante--;  //actualizo el timer de la animacion respectiva
    			        	
    		screen.Blit(npc.sprite);        		
    	}	
    }
    
    
    bool validaColisionFutura(NPC npc)
    {
    	bool resultado = false;        	
    	
    	foreach(NPC npcs in listaNpc)
    	{
    		if (npc.id != npcs.id)
    		{
    			if (npc.sprite.IntersectsWith(npcs.sprite))
    			{
    				npc.direccion = 0;
    				npc.sprite.Animate = false;
    				npcs.direccion = 0;		    			
    				npcs.sprite.Animate = false;
    				
    				msg_superior = "Intercepta "+npc.id.ToString() +" con "+ npcs.id.ToString() ;
    				
    				//definimos un timer para que el mensaje se muestre solo 2 segundos
    				hora_msg = DateTime.Now.AddSeconds(2).ToString("HH-mm-ss");
    				resultado = true;
    			}
    		}
    	} 	
    	return resultado;
    }
    


    ¿Como cargar la nieve?

    Aquí hay un punto importante ya que me quede 1 semana pegado haciendo el código, ya que se me cortaba mucho y no andaba si quiera a 15 FPS usando 250 copos de nieve. Lo que estaba haciendo era hacer un blit a un arreglo de sprites con forma de nieve, algo así:

    ArrayList listaCoposNieve  = new ArrayList ()
    
    //cargaba los "copos"
    for (int i = 0; i< 250; i++)
    {
    	Surface copo = new Surface("copoNieve.png"))
    	Sprite spr_copo = new Sprite(copo);
    	listaCoposNieve.Add(copo)
    }
    
    //los pintaba
    foreach(NPC npc in listaCoposNieve)
    {             
    	screen.Blit(spr_copo, new Point( random x, ramdom y ));       	
    }	
    

    Esto está mal ya que el rendimiento se dispara.
    La técnica es hacer una clase que derive de la clase Sprite y que se pinte con un TickEvent propio del Sprite. Esto quiere decir que el objeto tiene un propio Update, distinto al screen.Update que es general.

    public class CopoNieve : Sprite
    {
    	...	    	
    	public CopoNieve() : base(new Surface(4, 4))
    	{    	
    		base.Surface.Fill(Color.White);
    		base.Y = -1 * random.Next(5000 - base.Surface.Height);
    	}
    
    	public override void Update(TickEventArgs args)
    	{
    		float change = delta * 30;
    		this.Y += (int)change;
    		this.X += (int)Math.Ceiling(...);
    		...
    	}
    }
    

    Se recomienda usar variables tipo float y no enteros ya que los valores del sprite de cada copo, cuando caen, son más precisos y le dan un efecto más real que cuando se usan valores enteros.

    Math.Ceiling (valor flotante) retorna el entero más pequeño que sea igual o mayor que el número flotante. Ejemplo:
    Math.Ceiling(7.03) da 8
    Math.Ceiling(7.64) da 8
    Math.Ceiling(0.12) da 1
    Math.Ceiling(-0.12) da 0
    Math.Ceiling(-7.1) da -7
    Math.Ceiling(-7.6) da -7

    Luego implementamos la creación de estos 250 objetos. Aquí incluimos el copos.EnableTickEvent() que nos permitirá activar el auto tick para los updates (cada update será una "pintada" de uno de los 250 copos en pantalla):

    void CargaInicialNieve(int numeroCopos)
    {        	
    for (int i = 0; i < 250; i++)
      {
          copos.Add(new CopoNieve());
      }        	
    copos.EnableTickEvent();       	
    } 
    

    Bueno, estas fueron las pautas para codificar, ahora el código completo.


    Código fuente de este capítulo:

    //Archivo: Cuarto.cs
    //Autor: Dark-N
    //fecha: 20-11-2009
    
    using System;
    using System.Collections.Generic; //para eventos y otras cosas
    using System.Drawing; //colores
    using System.Collections;
    using System.Diagnostics;
    using SdlDotNet.Graphics; //para sprites
    using SdlDotNet.Core; //Eventos
    using SdlDotNet.Graphics.Sprites;  //textos y graficos
    using SdlDotNet.Input; //para teclado
    
    
    namespace TutorialSDLNet
    {
        class Cuarto
        {       
            private Surface screen;
            private int velocidad_mov = 2;
            private int resolucionX= 416;
            private int resolucionY= 320;
            private Surface arbol;
            private Surface suelo;
            private Surface personajes;
            private Sprite spr_arbol;
            private SdlDotNet.Graphics.Font fuente = new SdlDotNet.Graphics.Font("arial.TTF", 13);
            private ArrayList listaNpc = new ArrayList();    
            string msg_superior = "";
            string hora_msg = "";
            Rectangle rectSuelo = new Rectangle(32,32, 384, 284);
            Rectangle rectArboles = new Rectangle(16,16, 384, 284);
            int unidadPixel = 16;
            SpriteCollection copos = new SpriteCollection();
    
            NPC npc1;        
            NPC npc2;         
            NPC npc3;       
    
            private void Evento_Salir(object sender, QuitEventArgs e)
            {
                Events.QuitApplication();
            }
    
            private void Evento_Teclado(object sender, KeyboardEventArgs e)
            {          	
                if (e.Key == Key.Escape)
                    Events.QuitApplication();  
                
                 if (e.Key == Key.Space)
                    Reiniciar();  
            }
    
            public void Run()
            {         
                Video.WindowCaption = "Cuarto con NPCs";
                screen = Video.SetVideoMode(resolucionX, resolucionY);
                SdlDotNet.Core.Events.Fps = 60;
                
                arbol = new Surface("cap9_arbol.png").Convert(screen, true, false);
                
                spr_arbol = new Sprite(arbol);
                         
                suelo = new Surface("cap9_pasto.PNG").Convert(screen, true, false); //16 x 16 pixels
                personajes = new Surface("cap9_hojasprites.png").Convert(screen, true, false);
                
                npc1 = new NPC(1, new Size(24,32),new Rectangle(0,0,72,132), 100, 100, new Point(50,50));
                npc2 = new NPC(2, new Size(24,32),new Rectangle(72,0,72,132), 80, 90, new Point(200,150));           
                npc3 = new NPC(3, new Size(24,32),new Rectangle(144,0,216,126), 70, 60, new Point(280,250));
                
                npc1.sprite = CargaNPC(npc1.tamano, npc1.rectangulo);
                npc2.sprite = CargaNPC(npc2.tamano, npc2.rectangulo);
                npc3.sprite = CargaNPC(npc3.tamano, npc3.rectangulo);
                
                npc1.sprite.CurrentAnimation = "abajo";            
                npc1.sprite.Center = npc1.centroInicial;
                
                npc2.sprite.CurrentAnimation = "arriba";
               	npc2.sprite.Center = npc2.centroInicial;
                
                npc3.sprite.CurrentAnimation = "derecha";
                npc3.sprite.Center = npc3.centroInicial;           
                
                listaNpc.Add(npc1);
                listaNpc.Add(npc2);
                listaNpc.Add(npc3);            
                
               
                CargaInicialNieve(250); //250 Copos se ven a la vez
                
                Events.KeyboardDown += new EventHandler < KeyboardEventArgs >(Evento_Teclado);
                SdlDotNet.Core.Events.Tick += new EventHandler < TickEventArgs >(this.Evento_Tick);
                SdlDotNet.Core.Events.Quit += new EventHandler < QuitEventArgs >(this.Evento_Salir);
                Events.Run();
            }
            
            
            AnimatedSprite CargaNPC(Size size, Rectangle rect)
            {        
            	AnimatedSprite ap = new AnimatedSprite();
            	AnimationCollection an1 = new AnimationCollection();
                AnimationCollection an2 = new AnimationCollection();
                AnimationCollection an3 = new AnimationCollection();
                AnimationCollection an4 = new AnimationCollection();
            
            	Surface surf_temp = new Surface(72,132).Convert(screen, true, false);
                surf_temp.Blit(personajes, new Point(0,0), rect);            
                
                SurfaceCollection walkUp = new SurfaceCollection();
                walkUp.Add(surf_temp, size, 0);
                SurfaceCollection walkRight = new SurfaceCollection();
                walkRight.Add(surf_temp, size, 1);
                SurfaceCollection walkDown = new SurfaceCollection();
                walkDown.Add(surf_temp, size, 2);
                SurfaceCollection walkLeft = new SurfaceCollection();
                walkLeft.Add(surf_temp, size, 3);
                
                an1.Add(walkUp);
                ap.Animations.Add("arriba",an1);            
                
                an2.Add(walkRight);
                ap.Animations.Add("derecha",an2);
                
                an3.Add(walkDown);
                ap.Animations.Add("abajo",an3);
                
                an4.Add(walkLeft);
                ap.Animations.Add("izquierda",an4);            
                
                ap.TransparentColor = Color.Black;
      			ap.Transparent = true;	
      			
                return ap;            
            }
            
            
            //ciclo de juego (game-loop)
            private void Evento_Tick(object sender, TickEventArgs args)
            {        	      	
                screen.Fill(Color.Black);          
                pintaMensajes();
                pintaSuelo();
                pintaArboles();
                pintaNPCs();
                screen.Blit(copos);
                screen.Update();
            }
    		
            void pintaMensajes()
            {
            	TextSprite  textoSup = new TextSprite(msg_superior, fuente, Color.Yellow);
            	screen.Blit(textoSup,new Point(10,3)); 
            	
            	TextSprite  textoInf = new TextSprite("Presiona ESPACIO para reiniciar", fuente, Color.Yellow);
            	screen.Blit(textoInf,new Point(10,screen.Height-unidadPixel)); 
            	
            	TextSprite  textoDemo = new TextSprite("Demo por Dark-N", new SdlDotNet.Graphics.Font("arial.TTF", 11), Color.White);
            	screen.Blit(textoDemo,new Point(310,screen.Height-15));
            	        	
            	// borramos los mensajes pasados 1 segundo
            	if (msg_superior != "")
            	{
            		string hora_temp = DateTime.Now.ToString("HH-mm-ss");
            		if (hora_temp == hora_msg)
            			msg_superior = "";
            	}        		
            }
            
            void pintaArboles()
            {          	 
            	for (int i = unidadPixel; i <= rectArboles.Height; i=i+unidadPixel)  //linea vertical izquierda
            		screen.Blit(spr_arbol, new Point( unidadPixel, i ));
            	
            	for (int i = unidadPixel; i <= rectArboles.Height; i=i+unidadPixel)  //linea vertical derecha
            		screen.Blit(spr_arbol, new Point( rectArboles.Width, i ));
            	
            	for (int i = unidadPixel; i <= rectArboles.Width; i=i+unidadPixel)  //linea horiz superior
            		screen.Blit(spr_arbol, new Point( i, unidadPixel ));
            	
            	for (int i = unidadPixel; i <= rectArboles.Width; i=i+unidadPixel)  //linea horiz inferior
            		screen.Blit(spr_arbol, new Point( i, rectArboles.Height ));
            }
            
            void pintaSuelo()
            {
            	for (int i = rectSuelo.X; i <= rectSuelo.Width; i=i+unidadPixel)
            		for (int j = rectSuelo.Y; j <= rectSuelo.Height; j=j+unidadPixel)
            			screen.Blit(suelo, new Point( i, j ));        	
            }
            
            void pintaNPCs()
            {
            	Random rand = new Random(); 
            	
                foreach(NPC npc in listaNpc)
            	{     	                       	
            		if (npc.tiempoRestante == npc.intervaloMovimiento/2)
    	        	{
    	        		npc.sprite.Animate = false;
    	        		npc.direccion = 0;
    	        	}  	
    	        	
            		if (npc.tiempoRestante == 0 || npc.moverPrimeraVez == true)
    	        	{
    	        		npc.direccion = rand.Next(1,5);        	
    	        		npc.tiempoRestante = npc.intervaloMovimiento;
    	        		npc.moverPrimeraVez = false;        		
    	        	}   
    	        	
    	        	if (npc.direccion == 1) //arriba
    	        	{
    	        		if (npc.sprite.CurrentAnimation != "arriba")
    	        			npc.sprite.CurrentAnimation = "arriba";
    	        			
    	        		if (npc.sprite.Top > rectSuelo.Y)  
    	        		{	
    	        			int poc_anterior = npc.sprite.Y;
    	        			npc.sprite.Y = npc.sprite.Y - velocidad_mov;
    	        			
    	        			if (validaColisionFutura(npc) == true)
    	        				npc.sprite.Y = poc_anterior;
    	        			
    	        			npc.sprite.Animate = true;
    	        		}     		
    	        	}
    	        	else
    	        	if (npc.direccion == 2) //derecha
    	        	{
    	        		if (npc.sprite.CurrentAnimation != "derecha")
    	        			npc.sprite.CurrentAnimation = "derecha";
    	        		
    	        		if (npc.sprite.Right < rectSuelo.Width)
    	        		{	        			
    	        			int poc_anterior = npc.sprite.X;	        			
    	        			npc.sprite.X = npc.sprite.X + velocidad_mov;        			
    	        			
    	        			if (validaColisionFutura(npc) == true)
    	        				npc.sprite.X = poc_anterior;
    	        			
    	        			npc.sprite.Animate = true;
    	        		}
    	        	}
    	        	else
    	        	if (npc.direccion == 3) //abajo
    	        	{
    	        		if (npc.sprite.CurrentAnimation != "abajo")
    	        			npc.sprite.CurrentAnimation = "abajo";
    	        		
    	        		if (npc.sprite.Bottom < rectSuelo.Height)
    	        		{	        			
    	        			int poc_anterior = npc.sprite.Y;
    	        			npc.sprite.Y = npc.sprite.Y + velocidad_mov;  
    	        			
    	        			if (validaColisionFutura(npc) == true)
    	        				npc.sprite.Y = poc_anterior;
    	        			
    	        			npc.sprite.Animate = true;        			
    	        		}        	
    	        	}
    	        	else
    	        	if (npc.direccion == 4) //izquierda
    	        	{
    	        		if (npc.sprite.CurrentAnimation != "izquierda")
    	        			npc.sprite.CurrentAnimation = "izquierda";
    	        		
    	        		if (npc.sprite.Left > rectSuelo.X)
    	        		{
    	        			int poc_anterior = npc.sprite.X;
    	        			npc.sprite.X = npc.sprite.X - velocidad_mov;
    	        			
    	        			if (validaColisionFutura(npc) == true)
    	        				npc.sprite.X = poc_anterior;
    	        			
    	        			npc.sprite.Animate = true;        			
    	        		}
            		}
          	
    	        	npc.tiempoRestante--;  //actualizo el timer de la animacion respectiva
    	        		        	
            		screen.Blit(npc.sprite);        		
            	}	
            }
            
           
            
            #region Funciones Generales
            void Reiniciar()
            {
            	//CargaInicialNieve(2550);
            	foreach(NPC npc in listaNpc)
            	{
            	 	npc.sprite.Center = npc.centroInicial;
            	}        	 
            	msg_superior = "";        
            }
                         
            
            bool validaColisionFutura(NPC npc)
            {
            	bool resultado = false;        	
            	
    	    	foreach(NPC npcs in listaNpc)
    	    	{
    	    		if (npc.id != npcs.id)
    	    		{
    		    		if (npc.sprite.IntersectsWith(npcs.sprite))
    		    		{
    		    			npc.direccion = 0;
    		    			npc.sprite.Animate = false;
    		    			npcs.direccion = 0;		    			
    		    			npcs.sprite.Animate = false;
    		    			
    		    			msg_superior = "Intercepta "+npc.id.ToString() +" con "+ npcs.id.ToString() ;
    		    			
    		    			//definimos un timer para que el mensaje se muestre solo 2 segundos
    		    			hora_msg = DateTime.Now.AddSeconds(2).ToString("HH-mm-ss");
    		    			resultado = true;
    		    		}
    	    		}
    	    	} 
            	
            	return resultado;
            }
            
            
            void CargaInicialNieve(int numeroCopos)
            {        	
            	for (int i = 0; i < numeroCopos; i++)
                {
                    copos.Add(new CopoNieve());
                }        	
            	copos.EnableTickEvent();       	
            } 
            
            void RandomValores(ref int x, ref int y, ref int t)
            {
            	Random r = new Random(DateTime.Now.Millisecond);
    			x = r.Next(1,rectArboles.Width+1);			
    			y = r.Next(1,rectArboles.Height+1);
    			t = r.Next(1,6);			
            }          
            #endregion
                          
            	
            static void Main()
            {
                Cuarto ejemploNpc = new Cuarto();
                ejemploNpc.Run();
            }      
    	
        }
        
        
        public class NPC 
        {
        	public int id;
        	public AnimatedSprite sprite;
        	public Size tamano;
        	public Rectangle rectangulo;
        	public int direccion;
        	public bool moverPrimeraVez;
        	public int intervaloMovimiento;
        	public int tiempoRestante;
        	public Point centroInicial;   	
        	
        	public NPC(int id, Size t, Rectangle r, int intervalo, int tiempo, Point centroIni)
        	{
        		this.id = id;
        		this.tamano = t;
        		this.rectangulo = r; 
        		this.moverPrimeraVez = true;
        		this.intervaloMovimiento = intervalo;
        		this.tiempoRestante = tiempo;
        		this.centroInicial = centroIni;
        	}   	
        }
        
        
        public class CopoNieve : Sprite
        {
        	static Random random = new Random();
        	float speed;
            float wind;
            float delta = 0.05f;  
            int resolucionX = 416;
            int resolucionY = 320;
        	    	
        	public CopoNieve() : base(new Surface(4, 4))
        	{    	
        		base.Surface.Fill(Color.White);
                base.Surface.TransparentColor = Color.FromArgb(255, 0, 255);
                base.Rectangle = new Rectangle(this.Surface.Width, this.Surface.Height, 0, 0);
                
                Reset();
                
                base.Y = -1 * random.Next(5000 - base.Surface.Height);
        	}
        	
        	void Reset()
            {
                wind = random.Next(3) / 10.0f;
                base.X = (int)random.Next(-1 * (int)(wind * resolucionX), resolucionX - base.Surface.Width);
                base.Y = 0 - base.Width;
                speed = random.Next(180, 255);
                base.Surface.Alpha = (byte) speed;
                base.Surface.AlphaBlending = true;
            }
        	
        	public override void Update(TickEventArgs args)
            {
                float change = delta * 30;
    
                this.Y += (int)change;
                this.X += (int)Math.Ceiling(change * wind);
    
                if (this.Y > resolucionY)
                {
                    Reset();
                }
            }    	
        }
    }
    


    Una vez codificado, si al compilar no arroja errores, debe aparecerte algo como esto:



    Bajar proyecto para SharpDevelop 2.2 aquí.



    Anterior | Índice | Siguiente
    Volver a Página Principal

    blog comments powered by Disqus
    2003 - 2018    La Web de Dark-N