Casino online









Mercato forex






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:
Public Class Game
    '...

    'BallSpeed rappresenta la velocità della palla
    Private BallSpeed As Single = 3

    'Ball rappresenta la palla
    Private Ball As GameObject

    '...

    Protected Overrides Sub LoadContent()
        MyBase.LoadContent()

        Background = New GameObject(GetTexture("Background.png"))
        Background.Position = Background.Center

        Dim Size As New Viewport
        Size.Width = Background.Sprite.Width
        Size.Height = Background.Sprite.Height
        Size.X = 0
        Size.Y = 0
        Me.Graphics.GraphicsDevice.Viewport = Size
        Me.Graphics.PreferredBackBufferHeight = Size.Height
        Me.Graphics.PreferredBackBufferWidth = Size.Width
        Me.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 GameTime As GameTime)
        'Controlla i tasti premuti
        KeyboardCheck()

        'Cambia la posizione della palla a seconda della sua velocità
        Ball.Position += Ball.Velocity
        
        MyBase.Update(GameTime)
    End Sub

    Protected Overrides Sub Draw(ByVal gameTime As GameTime)
        Me.Graphics.GraphicsDevice.Clear(Color.White)

        Batch.Begin()

        Background.Draw(Batch)

        Bat.Draw(Batch)
        'Disegna la palla
        Ball.Draw(Batch)

        Batch.End()

        MyBase.Draw(gameTime)
    End Sub
End Class 
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
    '...
    
    '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 Obj As 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 BallRect As Rectangle = GetBounds(Ball)
        'E quello che contiene la mazza
        Dim BatRect As 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 GameTime As GameTime)
        KeyboardCheck()

        'Controlla le collisioni
        CollisionCheck()

        Ball.Position += Ball.Velocity

        MyBase.Update(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:
Private Sub CollisionCheck()
        'Costruisce il rettangolo che contiene la palla
        Dim BallRect As Rectangle = GetBounds(Ball)
        'E quello che contiene la mazza
        Dim BatRect As 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 < 5 Then
            '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.Height Then
            
        End If
    End Sub 
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...


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à)

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α


Dalla matematica al codice
Scriviamo la nuova procedura per calcolare la deviazione della palla:
    'Calcola la deviazione della palla contro un oggetto
    Private Sub CalculateBallVelocity(ByVal Obj As 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 DeltaX As Single = Ball.Position.X - Obj.Position.X
        'Calcola l'angolo di deviazione, Theta
        Dim DevAngle As Single = _
            (Math.Abs(DeltaX) / (Obj.Sprite.Width / 2)) * (Math.PI / 3)
        'Calcola il suo complementare, Alpha
        Dim AngleOnX As Single = MathHelper.PiOver2 - Math.Abs(DevAngle)

        'Calcola le componenti X e Y
        Dim YComponent As Single = BallSpeed * Math.Sin(AngleOnX)
        Dim XComponent As 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 < 0 Then
            XComponent = -XComponent
        End 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.Y Then
            Ball.Velocity = New Vector2(XComponent, -YComponent)
        Else
            Ball.Velocity = New Vector2(XComponent, YComponent)
        End If
    End Sub 
E usiamola in CollisionCheck:
    'Questa nuova procedura controlla le collisioni di ogni oggetto
    Private Sub CollisionCheck()
        Dim BallRect As Rectangle = GetBounds(Ball)
        Dim BatRect As 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 < 5 Then
            Ball.Velocity = _ 
                New Vector2(Ball.Velocity.X, -Ball.Velocity.Y)
        End If

        If Ball.Position.Y > Me.GraphicsDevice.Viewport.Height Then
            
        End If
    End Sub 
Ed ecco il risultato.





 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.