A4. Gestire le collisioni - Parte I
Ora è possibile spostare la mazza orizzontalmente, ma niente di più. Passiamo ad inserire la pallina: essa si muoverà da sola e dovrà rimbalzare sulla mazza. Il suo sprite è questo, preso sempre da Game Maker 6:
Ball: la palla
Come fatto in precedenza, associamo un oggetto GameObject alla palla, carichiamo lo sprite, e tutte le altre operazioni:
Facendo correre il programma, si dovrebbe vedere questo. Sicuramente non è un bello spettacolo osservare che la pallina attraversi la mazza come se non ci fosse. Dobbiamo rimediare con del nuovo codice per rintracciare, e gestire, la collisione tra questi due oggetti. Il procedimento è semplice: dato che gli sprite, e quindi le immagini, sono delimitati da un'area rettangolare (anche se lo sfondo è trasparente), basta verificare se questi rettangoli si sovrappongono:Public Class Game '... 'BallSpeed rappresenta la velocità della palla Private BallSpeedAs Single = 3 'Ball rappresenta la palla Private BallAs GameObject '... 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) 'Inizializza l'oggetto e carica lo sprite Ball =New GameObject(GetTexture("Ball.png")) 'Imposta la posizione della palla esattamente al centro 'dello schermo Ball.Position =New Vector2( _Me .Graphics.GraphicsDevice.Viewport.Width / 2, _Me .Graphics.GraphicsDevice.Viewport.Height / 2) 'Imposta le velocità della palla, tutta verso il basso Ball.Velocity =New Vector2(0, BallSpeed)End Sub '... Protected Overrides Sub Update(ByVal GameTimeAs GameTime) 'Controlla i tasti premuti KeyboardCheck() 'Cambia la posizione della palla a seconda della sua velocità Ball.Position += Ball.VelocityMy 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) 'Disegna la palla Ball.Draw(Batch) Batch.End()My Base.Draw(gameTime)End Sub End Class
Il risultato sarà questo. C'è ancora qualcosa che non va: la palla è comunque libera di attraversare i limiti della finestra. Dobbiamo riparare anche a questo, modificando il codice:Public Class Game '... 'Questa funzione ottiene il rettangolo che delimita l'area occupata 'da un oggetto GameObject e dal suo sprite. Bisogna ricordarsi che 'le proprietà X e Y di Rectangle si riferiscono al vertice 'superiore sinistro (Location). Per trovare le coordinate giuste, 'quindi, dato che Obj.Position indica il centro dell'oggetto, 'dobbiamo sottrarre a Position.X metà della larghezza 'e a Position.Y metà dell'altezza. Private Function GetBounds(ByVal ObjAs GameObject)As Rectangle 'Il costruttore di Rectangle accetta questi 4 parametri: 'New Rectangle(X, Y, Larghezza, Altezza) Return New Rectangle( _ Obj.Position.X - (Obj.Sprite.Width / 2), _ Obj.Position.Y - (Obj.Sprite.Height / 2), _ Obj.Sprite.Width, Obj.Sprite.Height)End Function '... 'Questa nuova procedura controlla le collisioni di ogni oggetto Private Sub CollisionCheck() 'Costruisce il rettangolo che contiene la palla Dim BallRectAs Rectangle = GetBounds(Ball) 'E quello che contiene la mazza Dim BatRectAs Rectangle = GetBounds(Bat) 'La funzione BallRect.Intersects(BatRect) determina se il 'rettangolo BallRect interseca il rettangolo BatRect. Se 'questo avviene, significa che la palla sta "sbattendo" 'contro la mazza e perciò deve rimbalzare If BallRect.Intersects(BatRect)Then 'Per far rimbalzare la palla, invertiamo la componente Y 'della sua velocità. In questo modo, se durante 'lo scontro stava andando verso il basso, ora andrà 'verso l'alto Ball.Velocity =New Vector2(Ball.Velocity.X, -Ball.Velocity.Y)End If End Sub '... Protected Overrides Sub Update(ByVal GameTimeAs GameTime) KeyboardCheck() 'Controlla le collisioni CollisionCheck() Ball.Position += Ball.VelocityMy Base.Update(GameTime)End Sub '... End Class
Ed ecco il risultato. Ora funziona tutto e la palla è in grado di rimbalzare sulle pareti e sulla mazza correttamente. Ma manca ancora qualcosa: certamente i rimbalzi sono perfetti, e forse anche troppo. Se la palla continuasse a rimbalzare a quel modo andrebbe sempre su e giù senza mai deviare. Dobbiamo dare alla mazza la capacità di far cambiare la traettoria della palla a seconda di dove la si colpisce. C'è bisogno di un po' di teoria...Private Sub CollisionCheck() 'Costruisce il rettangolo che contiene la palla Dim BallRectAs Rectangle = GetBounds(Ball) 'E quello che contiene la mazza Dim BatRectAs Rectangle = GetBounds(Bat) 'La funzione BallRect.Intersects(BatRect) determina se il 'rettangolo BallRect interseca il rettangolo BatRect. Se 'questo avviene, significa che la palla sta "sbattendo" 'contro la mazza e perciò deve rimbalzare If BallRect.Intersects(BatRect)Then 'Per far rimbalzare la palla, invertiamo la componente Y 'della sua velocità. In questo modo, se durante 'lo scontro stava andando verso il basso, ora andrà 'verso l'alto Ball.Velocity =New Vector2(Ball.Velocity.X, -Ball.Velocity.Y)End If 'Questo If controlla che la palla sia vicino ai margini laterali 'della finestra, ossia X<5 o X>(Larghezza-5). Tuttavia, bisogna 'ricordarsi che la palla può andare fuori dalla finestra se 'cade troppo in basso e il giocatore non è stato in grado 'di respingerla con la mazza. In quel caso, anche se la palla si 'trova agli estremi, non verrà deviata. If ((Ball.Position.X < 5)Or _ (Ball.Position.X >Me .GraphicsDevice.Viewport.Width - 5)) _And (Ball.Position.Y <Me .GraphicsDevice.Viewport.Height - 5)Then 'Inverte la componente X della velocità Ball.Velocity = _New Vector2(-Ball.Velocity.X, Ball.Velocity.Y)End If 'Allo stesso modo, se la palla sbatte sul margine in alto, verrà 'deviata indietro If Ball.Position.Y < 5Then 'Inverte la componente Y della velocità 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. 'Lasciamo vuoto questo spazio, perchè ce ne occuperemo dopo If Ball.Position.Y >Me .GraphicsDevice.Viewport.HeightThen End If End Sub
La trigonometria serve!
Allora, l'idea di fondo è questa: far deviare la pallina di un angolo tanto più grande quanto più essa è
spostata verso l'estremo della mazza, come mostrato in questo rudimentale disegno:
Deviazioni della traettoria
Se la palla sbatte esattamente al centro, tornerà indietro perpendicolarmente alla mazza. Lo so, non è esatto in quanto fisica, ma almeno dà un po' più di movimento e strategia al gioco.
Ora ammettiamo che il massimo angolo possibile (quello ottenuto colpendo la palla con gli estremi della mazza) sia, ad esempio, 60�. Nel caso si colpisca l'estremo destro, la deviazione sarà esattamente 60� sopra l'orizzontale; nel caso si colpisca l'estremo sinistro sarà 120� (ossia 60� verso sinistra). Poi, però, bisogna anche calcolare la velocità che, essendo un vettore di modulo costante BatSpeed, varierà le proprie componenti in funzione dell'angolo. Quindi, prendendo questo schema come riferimento:
Theta = angolo di deviazione
Alpha = complementare di Theta
w = larghezza della mazza
x = sfasamento dei centri sulle ascisse
t = traettoria deviata (= vettore velocità)
Alpha = complementare di Theta
w = larghezza della mazza
x = sfasamento dei centri sulle ascisse
t = traettoria deviata (= vettore velocità)
Dato che x (o Δx, come preferite) è la distanza del centro della palla rispetto al centro della mazza, sull'asse delle ascisse, possiamo considerare che, tanto più |Δx| è grande, tanto più grande sarà θ, ma solo fino a π/3 (ossia 60�). Quindi, possiamo ricavare θ ad esempio con questa formula:
θ = (|Δx| / (w/2) ) * π/3
Quindi se |Δx| = 0, θ = 0; se |Δx| = w/2 (ossia sbatte sull'estremo) θ = π/3.
Ora che abbiamo l'angolo, dobbiamo calcolare la velocità come vettore e quindi le sue componenti x e y. Avendo la sua lunghezza, che è costante e corrisponde a BallSpeed, è facile risalire alle componenti:
Vx = |V| * cosα
Vy = |V| * sinα
Vy = |V| * sinα
Dalla matematica al codice
Scriviamo la nuova procedura per calcolare la deviazione della palla:
'E usiamola in CollisionCheck:Calcola la deviazione della palla contro un oggetto Private Sub CalculateBallVelocity(ByVal ObjAs GameObject) 'Sfasamento sulle ascisse dei centri. Notare che se 'Ball è spostata verso sinistra, questo valore 'è negativo. Ecco perchè ho usato sempre il 'suo valore assoluto Dim DeltaXAs Single = Ball.Position.X - Obj.Position.X 'Calcola l'angolo di deviazione, Theta Dim DevAngleAs Single = _ (Math.Abs(DeltaX) / (Obj.Sprite.Width / 2)) * (Math.PI / 3) 'Calcola il suo complementare, Alpha Dim AngleOnXAs Single = MathHelper.PiOver2 - Math.Abs(DevAngle) 'Calcola le componenti X e Y Dim YComponentAs Single = BallSpeed * Math.Sin(AngleOnX)Dim XComponentAs Single = BallSpeed * Math.Cos(AngleOnX) 'Se DeltaX > 0, la palla ha sbattuto sulla parte destra 'dell'oggetto, e quindi le sua componente X sarà 'positiva. Se DeltaX < 0, invece, sarà negativa e 'punterà verso sinistra If DeltaX < 0Then XComponent = -XComponentEnd If 'Ora che l'abbiamo calcolata, la componente Y è sempre 'positiva, ma se la palla arriva dall'alto e sbattendo 'torna indietro, essa dovrà essere negativa. 'Questo If provvede ad aggiustare i parametri. If Ball.Position.Y <= Obj.Position.YThen Ball.Velocity =New Vector2(XComponent, -YComponent)Else Ball.Velocity =New Vector2(XComponent, YComponent)End If End Sub
'Ed ecco il risultato.Questa nuova procedura controlla le collisioni di ogni oggetto Private Sub CollisionCheck()Dim BallRectAs Rectangle = GetBounds(Ball)Dim BatRectAs Rectangle = GetBounds(Bat)If BallRect.Intersects(BatRect)Then 'Ora calcola tutta la traettoria 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 If Ball.Position.Y >Me .GraphicsDevice.Viewport.HeightThen End If End Sub
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



