Sono veramente contento di essere arrivato fino a questo punto, pensavo che mi sarei rotto le palle mooooolto prima, invece vedere che si sono tutti i giorni dei lettori (grazie google Analytics) mi mette voglia di scrivere... lasciate qualche commento come segno di vita dai! fa venire voglia di scrivere sapere che c'è qualcuno interessato :D
Comunque, tornando a noi, ci terrei ad inserire subito qui sotto il video di questa lezione perchè a mio parere fa venire voglia di leggerla e concluderla :D
Anteprima Video
L'argomento principale come da mega titolo è la generazione casuale di sprite, se avete seguito le altre lezioni saprete che ad ogni collisione vengono decrementati punti vita, che siamo in grado di pilotare la nostra astronave e che abbiamo uno spritemanager per gestire le grafiche 2d.
Quello che aggiungeremo ora sarà un sistema temporizzato che permetterà la generazione in posizioni casuali di altri sprite. Oltretutto produrremmo due tipologie di sprite in base alla velocità delle astronavi.
Come potete vedere gli sprite che ho utilizzato sono differenti da quelli presenti negli altri tutorial, volevo dare un pò più di gusto a questa lezione e ho deciso di utilizzare delle astronavi di Battlestar Galactica così a qualche mio amico verrà sicuuuramente voglia di seguire questi turorial.... :P( ne approfitto per ringraziare martin studio sito dal quale ho reperito questi ottimi sprite http://martinstudio.kings-field.com/ sono fatti veramente bene!!! ).
Per modificare le immagini aggiunte nelle precedenti lezioni basta andare nel punto dove avete salvato il progetto (solitamente Documenti\Visual Studio 2008\Projects\) arrivare alla cartella content del progetto e sostituire le immagini mantenendo gli stesis nomi (per evitare di dover modificare il codice). Le immagini le trovate nei sorgenti a fondo pagina o direttamente al link segnalato sopra.
A questo punto partirei spedito con il codice senza perdere altro tempo!
Partiamo dal file Game1.cs qui ho aggiunto un'immagine di sfondo e il riferimento all'oggetto rnd di tipo Random sul quale faremo affidamento per generare i nostri valori casuali.
Come al solito copio tutto il codice per darvi una visibilità magigore di quello che ho modificato (segno in grasseto le modifiche)
GAME1
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace input
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int life;//Vite del giocatore
SpriteFont gui_life;//GUI
Texture2D tex_universe;//Background universo
//Proprietà per Random
public Random rnd { get; private set; }
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
rnd = new Random();
}
protected override void Initialize()
{
SpriteManager spritemanager = new SpriteManager(this);
Components.Add(spritemanager);
life = 100;
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
tex_universe= Content.Load<Texture2D>(@"universe");
gui_life = Content.Load<SpriteFont>("Arial");
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(tex_universe,
new Rectangle(0,0,Window.ClientBounds.Width,Window.ClientBounds.Height),Color.White);
spriteBatch.DrawString(gui_life, "LIFE: " + life, new Vector2(10, 10), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
public int Life {
get { return life; }
set {
life = value;
if (life == 0) {
Exit();
}
}
}
Non mi dilungo troppo sull'inserimento dell'immagine di sfondo, semplicemente aggiungete un contentuto immagine nei content e lo inserite tramite spritebatch prima di disegnare la stringa della vita (in questo modo verrà stampato prima di quest'ultima che si posizionerà al livello superiore).
Per quanto riguarda l'oggetto rnd, molto semplicemente ho aggiunto una proprietà set e get in testa e ho istanziato l'oggetto nel costruttore, come già anticipato utilizzeremo le funzionalità della classe random per generare numeri casuali.
Ora entriamo nello spritemanager dove si presentano le vere modifiche!
Eccovi il codice:
SPRITEMANAGER
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace input
{
public class SpriteManager : Microsoft.Xna.Framework.DrawableGameComponent
{
SpriteBatch spriteBatch;
List<Sprite> spriteList = new List<Sprite>();
ControlledSprite player;
Texture2D tex1, tex2, texp;
Vector2 posp;
//tempi di attesa minimi e massimi per lo spawn dei nemici
int minEnemySpawn = 100;//Millisecondi
int maxEnemySpawn = 600;//Millisecondi
int nextSpawn = 0;
public SpriteManager(Game game)
: base(game)
{
// TODO: Construct any child components here
}
public override void Initialize()
{
//Lasciamo solo la posizione iniziale del player
posp = new Vector2(500.0f, 400.0f);
//impostiamo il prossimo spawn
resetTimer();
base.Initialize();
}
protected override void LoadContent()
{
tex1 = Game.Content.Load<Texture2D>("enemy");
tex2 = Game.Content.Load<Texture2D>("enemy2");
texp = Game.Content.Load<Texture2D>("spaceship");
spriteBatch = new SpriteBatch(Game.GraphicsDevice);
player = new ControlledSprite(texp, posp, new Vector2(3.0f, 3.0f));
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
//Aggiorno il giocatore
player.Update(gameTime);
//Sotraggo il tempo passato dallo spawn time
nextSpawn -= gameTime.ElapsedGameTime.Milliseconds;
//Se lo spawn time è sceso sotto zero, genero il nuovo nemico
if (nextSpawn < 0) {
spawnEnemy();
}
//Aggiorno tutti gli elementi della lista, al posto del foreach usiamo un for
for (int i = 0; i < spriteList.Count; i++) {
Sprite s = spriteList[i];
s.Update(gameTime);
//Controllo se uno degli sprite nemici collide contro il giocatore
if (s.collisionRect.Intersects(player.collisionRect)) {
//Esco dal gioco
--((Game1)Game).Life;
spriteList.RemoveAt(i);
--i;
}
if(s.OutOfView(Game.Window.ClientBounds)){
spriteList.RemoveAt(i);
--i;
}
}
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
//Disegno il giocatore
player.Draw(gameTime,spriteBatch);
//Disegno tutti gli elementi della lista
foreach (Sprite s in spriteList)
{
s.Draw(gameTime,spriteBatch);
}
base.Draw(gameTime);
spriteBatch.End();
}
private void resetTimer() {
//Imposta il tempo del prossimo spawn
nextSpawn = ((Game1)Game).rnd.Next(minEnemySpawn, maxEnemySpawn);
}
//Genera un nuovo nemico se il tempo di spawn è passato
private void spawnEnemy() {
int xpos = ((Game1)Game).rnd.Next(0, 600);
int xspeed = ((Game1)Game).rnd.Next(-3, 3);
int yspeed = ((Game1)Game).rnd.Next(2, 7);
Texture2D texture;
if (yspeed > 5)
{
texture = tex2;
}
else {
texture = tex1;
}
Vector2 speed = new Vector2(xspeed,yspeed);
Vector2 position = new Vector2(xpos,-50);
spriteList.Add(new AutoSprite(texture, position, speed));
resetTimer();
}
}
}
Prima di tutto vi spiego la logica che vogliamo seguire.
Definiamo che esiste un tempo minimo e un tempo massimo entro il quale creiamo una nuova astronave.
Generiamo un numero casuale
nextSpawn
compreso tra questi due parametri, e ad ogni loop di gioco sottraiamo da questo numero il tempo trascorso dal ciclo successivo.
Quendo il numero nextSpawn sarà minore di 0, creiamo una nuova astronave e resettiamo nextSpawn ripescando un numero casuale tra i paramentri sopra... e così via.
In testa, come variabili di classe settiamo i parametri
//tempi di attesa minimi e massimi per lo spawn dei nemiciChe rappresentano il tempo minimo e il tempo massimo di spawn dei nemici e inizializiamo nextSpawn.
int minEnemySpawn = 100;//Millisecondi
int maxEnemySpawn = 600;//Millisecondi
int nextSpawn = 0;
Nella initialize lanciamo la funzione resetTimer che come vedete si occupa di trovare un numero casuale utilizzando l'oggetto rnd che abbiamo creato in game1.cs. Utilizza la funzione
nextSpawn = ((Game1)Game).rnd.Next(minEnemySpawn, maxEnemySpawn);e prende i due parametri min e max come estremi del range entro il quale pescare il numero casuale.
Nella Update grazie all'oggetto gameTime (finalmente ho un esempio di utilizzo per questo valore ) sottraiamo il tempo trascorso dal precedente ciclo a nextSpawn attuale, se nextSpawn è sceso sotto zero, lanciamo la funzione spawnEnemy.
//Sotraggo il tempo passato dallo spawn timePer quanto riguarda la spawnEnemy genera una posizione e una velocità casuale, utilizzando sempre l'oggetto rnd
nextSpawn -= gameTime.ElapsedGameTime.Milliseconds;
//Se lo spawn time è sceso sotto zero, genero il nuovo nemico
if (nextSpawn < 0) {
spawnEnemy();
}
a questo punto in base alla velocità generà un oggetto AutoSprite con due differenti texture. Se la velocità è superiore a 5 genera Cylone minacciosissimo :P e più difficile da schivare. In entrambi i casi aggiunge l'astronave alla lista di Sprite della spriteManager.
Un'ultima nota, dato che a questo punto si genereranno centinaia di astronavi, è bene occuparsi anche della memoria e fare in modo che quando le astronavi lasciano l'area di gioco, queste vengano cancellate dalla lista di sprite dato che sarebbe inutile continuare a occuparsi della loro posizione e fare check di collisioni! Sempre nella update trovate questo codice utile allo scopo sopra descritto:
if(s.OutOfView(Game.Window.ClientBounds)){la funzione OutOfView va inserita a fondo della classe sprite.cs eccola qui...
spriteList.RemoveAt(i);
--i;
}
//lo sprite è nella finestra?
public bool OutOfView(Rectangle myRect){
if (position.X < 0 ||
position.X > myRect.Width ||
position.Y > myRect.Height)
{
return true;
}
else return false;
}
Signori... anche questa volta ci siamo riusciti... abbiamo finito e il nostro gioco inizia a prendere forma.
Codici Sorgenti Finali:
5.generatore.zip
8 commenti:
forse c'è un errore nel testo
la funzione OutOfView va inserita a fondo della classe Sprite.cs e non della classe Game1.cs.
Assolutamente vero correggo subito ! grazie per la segnalazione
Pian piano imparo anche io grazie a te Yari...e grazie alla scelta degli sprite di BSG ;)
public bool OutOfView(Rectangle myRect){
return (position.X < 0 ||
position.X > myRect.Width ||
position.Y > myRect.Height);
}
Ciao ma in tutto questo come si integra l'audio????? Sono curiosissimo!! Vorrei farti i complimenti er la tua guida la trovo molto semplice e funzionale!!!!!
Grazie, sei un mito!
Grande, articolo molto interessante! Continua così, continuerò sicuramente a leggere!
Grazie 1000 ! Ne serve tanta di gente come te :)
Posta un commento