A6. Gestire i livelli
Sicuramente non possiamo pensare di inserire tutti i blocchi da codice controllando i livelli con degli If. Dobbiamo fare in modo che il processo di caricamento dei livelli venga automatizzato dal programma: ogni livello sarà salvato su un file con estensione *.lvl, letto e trasportato sul gioco. Dobbiamo, quindi, creare un engine.
Per prima cosa servono altre quattro utilissime variabili globali:
'Visto che abbiamo dichiarato Lives, aggiustiamo il codice di CollisionCheck nel caso in cui la palla vada fuori dal margine inferiore:Il livello a cui si è arrivati Private LevelAs Int16 = 0 'Numero di vite a disposizione del giocatore Private LivesAs Int16 = 3 'Determina se il livello sia completato Private LevelCompletedAs Boolean = True 'Determina se il gioco sia completato Private GameCompletedAs Boolean = False
'Ora si deve pensare a come scrivere i file per comunicare la posizione di uno o più blocchi sullo schermo. Dato che in questo gioco è molto frequente avere una o più file consecutive di elementi, sarebbe utile studiare un modo per dichiarare la posizione non di un solo blocco ma di molti. Io ho pensato a questa formattazione:Se, invece, supera il margine inferiore della finestra, allora si perde 'una vita. If Ball.Position.Y >Me .Graphics.GraphicsDevice.Viewport.HeightThen Lives -= 1 'Sposta la palla al centro e la fa muovere verso il basso Ball.Position =New Vector2( _Me .GraphicsDevice.Viewport.Width / 2, _Me .GraphicsDevice.Viewport.Height / 2) Ball.Velocity =New Vector2(0, BallSpeed)End If
[Nome blocco] = [X] > [Xmax], [Y] > [Ymax]Dove i caratteri ">" indicano che si debbano porre tanti blocchi quanti ne servono per riempire lo spazio tra [X] e [Xmax] (o tra [Y] e [Ymax]). Ecco alcuni esempi:
Block01 = 30, 30
Block01 = 30 > 250, 30
Block01 = 30, 30 > 250
Block01 = 30 > 250, 30 > 250
Per ottenere questo risultato, dovremo scrivere una nuova procedura AdvanceLevel, che legga un file e trasformi le dichiarazioni ivi contenute in realtà:
Ora possiamo scrivere il primo livello, ad esempio:Imports Microsoft.Xna.FrameworkImports Microsoft.Xna.Framework.InputImports Microsoft.Xna.Framework.GraphicsPublic Class GameInherits Microsoft.Xna.Framework.GamePrivate GraphicsAs GraphicsDeviceManagerPrivate BatchAs SpriteBatchPrivate BatSpeedAs Single = 3Private BallSpeedAs Single = 3Private BackgroundAs GameObject 'Il livello a cui si è arrivati Private LevelAs Int16 = 0 'Numero di vite a disposizione del giocatore Private LivesAs Int16 = 3 'Determina se il livello sia completato Private LevelCompletedAs Boolean = True 'Determina se il gioco sia completato Private GameCompletedAs Boolean = FalsePrivate BatAs GameObjectPrivate BallAs GameObjectPrivate BlocksAs New List(Of Block)Private Function GetTexture(ByVal NameAs String )As Texture2DReturn Texture2D.FromFile(Me .Graphics.GraphicsDevice, _My .Application.Info.DirectoryPath & "\" & Name)End Function Private Function GetBounds(ByVal ObjAs GameObject)As RectangleReturn New Rectangle( _ Obj.Position.X - (Obj.Sprite.Width / 2), _ Obj.Position.Y - (Obj.Sprite.Height / 2), _ Obj.Sprite.Width, Obj.Sprite.Height)End Function Private Sub KeyboardCheck()Dim KeyStateAs KeyboardState = Keyboard.GetStateIf KeyState.IsKeyDown(Keys.Right)Then Bat.Position +=New Vector2(BatSpeed, 0)End If If KeyState.IsKeyDown(Keys.Left)Then Bat.Position +=New Vector2(-BatSpeed, 0)End If If KeyState.IsKeyDown(Keys.Escape)Then Me .Exit()End If End Sub Private Sub CalculateBallVelocity(ByVal ObjAs GameObject)Dim DeltaXAs Single = Ball.Position.X - Obj.Position.XDim DevAngleAs Single = _ (Math.Abs(DeltaX) / (Obj.Sprite.Width / 2)) * (Math.PI / 3)Dim AngleOnXAs Single = MathHelper.PiOver2 - Math.Abs(DevAngle)Dim YComponentAs Single = BallSpeed * Math.Sin(AngleOnX)Dim XComponentAs Single = BallSpeed * Math.Cos(AngleOnX)If DeltaX < 0Then XComponent = -XComponentEnd If If Ball.Position.Y <= Obj.Position.YThen Ball.Velocity =New Vector2(XComponent, -YComponent)Else Ball.Velocity =New Vector2(XComponent, YComponent)End If End Sub Private Sub CollisionCheck()Dim BallRectAs Rectangle = GetBounds(Ball)Dim BatRectAs Rectangle = GetBounds(Bat)Dim BouncedAs Boolean = FalseIf BallRect.Intersects(BatRect)Then CalculateBallVelocity(Bat)End If If ((Ball.Position.X < 5)Or _ (Ball.Position.X >Me .GraphicsDevice.Viewport.Width - 5)) _And (Ball.Position.Y <Me .GraphicsDevice.Viewport.Height - 5)Then Ball.Velocity = _New Vector2(-Ball.Velocity.X, Ball.Velocity.Y)End If If Ball.Position.Y < 5Then Ball.Velocity = _New Vector2(Ball.Velocity.X, -Ball.Velocity.Y)End If 'Se, invece, supera il margine inferiore della finestra, 'allora si perde una vita. If Ball.Position.Y >Me .GraphicsDevice.Viewport.HeightThen Lives -= 1 'Sposta la palla al centro e la fa muovere verso il basso Ball.Position =New Vector2( _Me .GraphicsDevice.Viewport.Width / 2, _Me .GraphicsDevice.Viewport.Height / 2) Ball.Velocity =New Vector2(0, BallSpeed)End If Dim HitBlocksAs New List(Of Block)For Each BlockAs BlockIn BlocksDim BlockRectAs Rectangle = GetBounds(Block)If BallRect.Intersects(BlockRect)And (Not Bounced)Then Ball.Velocity = _New Vector2(Ball.Velocity.X, -Ball.Velocity.Y) Bounced = True Block.Life -= 1If Block.Life = 0Then Block.Enabled = False HitBlocks.Add(Block)Else Block.Sprite = GetTexture("Block" & _ Block.Life.ToString.PadLeft(2, "0") & ".png")End If End If Next For Each BlockAs BlockIn HitBlocks Blocks.Remove(Block)Next End Sub 'AdvanceLevel verrà chiamata una volta 'verificato che Blocks non contenga più nessun blocco Private Sub AdvanceLevel() 'Aumenta il livello di 1 Level += 1 LevelCompleted = FalseIf Not IO.File.Exists( _My .Application.Info.DirectoryPath & _ "\Level" & Level & ".lvl")Then 'Se non esiste il file, significa che non ci sono più 'livelli, quindi il gioco è finito GameCompleted = TrueEnd If Dim ReaderAs New IO.StreamReader( _My .Application.Info.DirectoryPath & _ "\Level" & Level & ".lvl")Dim LineAs String Do While Not Reader.EndOfStream 'Legge la linea Line = Reader.ReadLine 'Se è vuota, la salta If String .IsNullOrEmpty(Line)Then Continue Do End If 'Spezza la linea in base al carattere "=" Dim Vals()As String = Line.Split("=") 'Il primo argomento è il nome del blocco Dim NameAs String = Vals(0).Trim 'Il secondo le coordinate Dim ArgAs String = Vals(1).TrimIf Arg.Contains(",")Then 'Prosegue, spezzando Args in base a "," Dim Coords()As String = Arg.Split(",") 'Il primo valore sarà l'ascissa Dim CoXAs String = Coords(0).Trim 'Il secondo l'ordinata Dim CoYAs String = Coords(1).Trim 'Area determina la porzione di schermo che 'dovrà essere coperta dai blocchi Dim AreaAs New Rectangle 'Se l'ascissa contiene >, signifca che i blocchi 'occuperanno un'area estesa in larghezza If CoX.Contains(">")Then 'Ottiene [X] e [Xmax] Dim Xs()As String = CoX.Split(">")Dim MinXAs Int16 = CInt(Xs(0).Trim)Dim MaxXAs Int16 = CInt(Xs(1).Trim) 'Imposta la Location dell'area su [X] Area.X = MinX 'E la sua larghezza su [Xmax]-[X] Area.Width = MaxX - MinXElse 'Altrimenti, c'è un solo blocco 'in larghezza Area.X = CInt(CoX) Area.Width = 0End If 'La stessa cosa per le Y If CoY.Contains(">")Then Dim Ys()As String = CoY.Split(">")Dim MinYAs Int16 = CInt(Ys(0).Trim)Dim MaxYAs Int16 = CInt(Ys(1).Trim) Area.Y = MinY Area.Height = MaxY - MinYElse Area.Y = CInt(CoY) Area.Height = 0End If 'Con due cicli for, popola l'area seleazionata. 'Su X usiamo Step 35, poichè i blocchi sono 'larghi 32 pixel, e inseriamo 3 pixel di distanza 'tra ognuno. E questo anche su Y For XAs Int16 = Area.XTo (Area.X + Area.Width)Step 35For YAs Int16 = Area.YTo (Area.Y + Area.Height)Step 19 'Inizializza un nuovo blocco in base al nome Dim BAs New Block(GetTexture(Name & ".png")) 'Imposta la sua posizione B.Position =New Vector2(X, Y) B.Name = Name 'Se il nome contiene Block, ottiene la sua "vita" 'dal numero in fondo al nome If Name.StartsWith("Block")Then B.Life = CInt(Name.Remove(0, Name.Length - 2))End If 'Lo aggiunge alla lista Blocks.Add(B)Next Next End If Loop Reader.Close()End Sub Sub New ()Me .Graphics =New GraphicsDeviceManager(Me )Me .Content.RootDirectory = "Content"End Sub Protected Overrides Sub Initialize() Batch =New SpriteBatch(Me .Graphics.GraphicsDevice)Me .IsMouseVisible = TrueMy Base.Initialize()End Sub Protected Overrides Sub LoadContent()My Base.LoadContent() Background =New GameObject(GetTexture("Background.png")) Background.Position = Background.CenterDim SizeAs New Viewport Size.Width = Background.Sprite.Width Size.Height = Background.Sprite.Height Size.X = 0 Size.Y = 0Me .Graphics.GraphicsDevice.Viewport = SizeMe .Graphics.PreferredBackBufferHeight = Size.HeightMe .Graphics.PreferredBackBufferWidth = Size.WidthMe .Graphics.ApplyChanges() Bat =New GameObject(GetTexture("Bat.png")) Bat.Position =New Vector2( _Me .Graphics.GraphicsDevice.Viewport.Width / 2, _Me .Graphics.GraphicsDevice.Viewport.Height - 40) Ball =New GameObject(GetTexture("Ball.png")) Ball.Position =New Vector2( _Me .Graphics.GraphicsDevice.Viewport.Width / 2, _Me .Graphics.GraphicsDevice.Viewport.Height / 2) Ball.Velocity =New Vector2(0, BallSpeed) 'Passa al primo livello AdvanceLevel()End Sub Protected Overrides Sub UnloadContent()My Base.UnloadContent()End Sub Protected Overrides Sub Update(ByVal GameTimeAs GameTime) KeyboardCheck() CollisionCheck() Ball.Position += Ball.Velocity 'Se non ci sono più blocchi, passa al livello dopo If Blocks.Count = 0Then AdvanceLevel()End If My Base.Update(GameTime)End Sub Protected Overrides Sub Draw(ByVal gameTimeAs GameTime)Me .Graphics.GraphicsDevice.Clear(Color.White) Batch.Begin() Background.Draw(Batch) Bat.Draw(Batch) Ball.Draw(Batch) 'Disegna tutti i blocchi For Each BlockAs GameObjectIn Blocks Block.Draw(Batch)Next Batch.End()My Base.Draw(gameTime)End Sub End Class
Block01 = 100 > 200, 30 > 130 Block01 = 890 > 990, 30 > 130 Block01 = 300 > 700, 300 > 370
Risultato
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



