Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Silverlight 8-Ball

In this article, I will discuss how I have built a 2 player 8-Ball game in Silverlight (play here). I will explain how I used the Expression tools to design the graphics and various .NET techniques to enable user control and game animation.
Coding4Fun at Clarity

Difficulty: Intermediate
Time Required: 6-10 hours
Cost: Free
Software: Visual Studio 2008 Beta 2, Silverlight 1.1 Alpha Refresh, Expression Design, Expression Blend 2 Preview
Hardware:
Download: Download

In this article, I will discuss how I have built a 2 player 8-Ball game in Silverlight (play here). I will explain how I used the Expression tools to design the graphics and various .NET techniques to enable user control and game animation. 

The following components must be implemented to complete this game:

  1. Graphics (pool table, pool ball, and pool stick)
  2. Vector animation and collision physics
  3. User interaction 
  4. Game state and control

Before I continue, I'd like to point out that I did not originate all of the code and XAML for this solution. I came up with the idea of creating a Silverlight 8-Ball game after stumbling upon a 2-D “bouncing bubble” animation here: http://www.bubblemark.com/ created by Alexey Gavrilov. 

The original intent for this solution was to compare the performance of the bouncing bubbles across various platforms.  I found this very useful, but was personally interested in making something fun and interactive out of it. 

Getting Started

Prerequisites

The following tools were used to implement this solution:

  • Visual Studio 2008 Beta 2
  • Silverlight 1.1 Alpha Refresh 
  • Expression Design
  • Expression Blend
Learning more about game development

If you'd like to learn more about game development, I found a number Coding4Fun posts under Gaming helpful.  In particular, the 2D Game Primer by "ZMan" gives a good overview of the basics (e.g. GameLoop, Sprites, etc.)

Graphics

I am not much of a graphic designer.  In fact, I've spent the majority of my career designing and implementing line of business apps for enterprise customers (typically not a very graphically intensive task).  So I believe it is a decent testament to the effectiveness of the Microsoft Expression suite that I could, in short order, create the graphics for this game. 

I used Expression Design to build the graphics of the pool table.  Modeling my design after the standard dimension of a 9 foot billiards table, I drew the main structure as a rectangle with rounded corners.  I drew a smaller green rectangle centered over it for the playing surface (of course following standard dimensions).  Next, I created 6 black circles placed one level behind the playing surface to give the impression of pockets.  Lastly, I added the wood texture to the outside rectangle (which was super easy as there is a set of wood textures available by default), and drew a simple rounded rectangle beneath the table to serve as a status bar. 

I also created the graphics for my pool stick using Expression Design.  This consisted of a few tapered polygons and 2 half circles for the butt and tip.

After exporting the associated XAML, I imported each element into my project.  From here, I could view and refine the design through a scaled down visual editor or the associated XAML.

Lastly, I inspected the XAML markup for the pool ball I reused from Alexey's Bubblemark solution.  I needed to understand the properties of this object so that I could modify the color of the balls later on.

Vector Animation and Collision Physics

The root of Silverlight8Ball is a Storyboard (x:Name=”GameLoop”) that acts as a triggering mechanism for determining ball velocity, direction, and redirection due to collision. Whereas some Storyboards are implemented with predetermined paths and timelines directly in XAML, the action taken at each tick must be determined dynamically in this game. So we simply set the duration to 00:00:0, and handle the Completed event to trigger our positioning logic. This design effectively creates what game developers call a “Game Loop” that triggers continuous processing to determine application state.

The resulting “GameLoop_Completedhandler” becomes the root processing agent for all of the game's ball movement logic.  

C#

   1: void GameLoop_Completed(object sender, EventArgs e)
   2: {
   3:     switch (m_ActionState)
   4:     {
   5:         case m_ActionStates.BallsMoving:
   6:  
   7:             // prep
   8:             List<Ball> removeList = new List<Ball>();
   9:             bool someBallsAreMoving = false;
  10:  
  11:             // move each ball
  12:             foreach (Ball ball in m_GameBalls)
  13:             {
  14:                 ball.Move();
  15:                 if (ball.InPocket) removeList.Add(ball);
  16:                 else if (ball.IsMoving) someBallsAreMoving = true;
  17:             }
  18:  
  19:             // store balls sunk on shot and update ui 
  20:             foreach (Ball ball in removeList)
  21:             {
  22:                 sunkBalls.Add(ball);
  23:                 RemoveBall(ball);
  24:             }
  25:  
  26:             if (someBallsAreMoving)
  27:             {
  28:                 // update vectors for ball collisions
  29:                 for (int i = 0; i < m_GameBalls.Count; i++)
  30:                 {
  31:                     for (int j = i + 1; j < m_GameBalls.Count; j++)
  32:                     {
  33:                         m_GameBalls[i].DoCollide(m_GameBalls[j]);
  34:                     }
  35:                 }
  36:             }
  37:             else
  38:             {
  39:                 // determine shot results
  40:                 ShotResults results = EvaluateShot();
  41:  
  42:                 // apply state and ui changes
  43:                 UpdateGameState(results);
  44:             }
  45:  
  46:             break;
  47:     }
  48:  
  49:     // restart the storyboard
  50:     if (m_IsRunning)
  51:     {
  52:         GameLoop.Begin();
  53:     }
  54:  
  55: }

VB

   1: Private Sub GameLoop_Completed(ByVal sender As Object, ByVal e As EventArgs)
   2:  
   3:     Select Case m_ActionState
   4:  
   5:         Case m_ActionStates.BallsMoving
   6:  
   7:             ' prep
   8:             Dim removeList As List(Of Ball) = New List(Of Ball)()
   9:             Dim someBallsAreMoving As Boolean = False
  10:  
  11:             ' move each ball
  12:             For Each ball As Ball In m_GameBalls
  13:                 ball.Move()
  14:                 If ball.InPocket Then
  15:                     removeList.Add(ball)
  16:                 ElseIf ball.IsMoving Then
  17:                     someBallsAreMoving = True
  18:                 End If
  19:             Next ball
  20:  
  21:             ' store balls sunk on shot and update ui 
  22:             For Each ball As Ball In removeList
  23:                 sunkBalls.Add(ball)
  24:                 RemoveBall(ball)
  25:             Next ball
  26:  
  27:             If someBallsAreMoving Then
  28:                 ' update vectors for ball collisions
  29:                 For i As Integer = 0 To m_GameBalls.Count - 1
  30:                     For j As Integer = i + 1 To m_GameBalls.Count - 1
  31:                         m_GameBalls(i).DoCollide(m_GameBalls(j))
  32:                     Next j
  33:                 Next i
  34:             Else
  35:                 ' determine shot results
  36:                 Dim results As ShotResults = EvaluateShot()
  37:  
  38:                 ' apply state and ui changes
  39:                 UpdateGameState(results)
  40:             End If
  41:  
  42:     End Select
  43:  
  44:     ' restart the storyboard
  45:     If m_IsRunning Then
  46:         GameLoop.Begin()
  47:     End If
  48:  
  49: End Sub

The above code snippets demonstrate the full functionality of our GameLoop.  However, the primary animation and collision logic are handled by "ball.Move();" and "m_GameBalls[i].DoCollide(m_GameBalls[j]);" lines (in C# example).  The Move() function of each ball applies its current vector (i.e. x and y "velocities") to determine its next position and updates the ball's UI element coordinates.  Once the ball is advanced, the DoCollide function checks if two balls have collided.  If so, their vectors are adjusted accordingly.

C#

   1: public bool DoCollide(Ball b)
   2: {
   3:     // calculate some vectors 
   4:     double dx = this._x - b._x;
   5:     double dy = this._y - b._y;
   6:     double dvx = this._vx - b._vx;
   7:     double dvy = this._vy - b._vy;
   8:     double distance2 = dx * dx + dy * dy;
   9:     
  10:     if (Math.Abs(dx) > this._d || Math.Abs(dy) > this._d)
  11:         return false;
  12:     if (distance2 > this._d2)
  13:         return false;
  14:  
  15:     // make absolutely elastic collision
  16:     double mag = dvx * dx + dvy * dy;
  17:  
  18:     // test that balls move towards each other    
  19:     if (mag > 0)
  20:         return false;
  21:  
  22:     mag /= distance2;
  23:  
  24:     double delta_vx = dx * mag;
  25:     double delta_vy = dy * mag;
  26:  
  27:     this._vx -= delta_vx;
  28:     this._vy -= delta_vy;
  29:  
  30:     b._vx += delta_vx;
  31:     b._vy += delta_vy;
  32:  
  33:     return true;
  34: }

VB

   1: Public Function DoCollide(ByVal b As Ball) As Boolean
   2:     ' calculate some vectors 
   3:     Dim dx As Double = Me._x - b._x
   4:     Dim dy As Double = Me._y - b._y
   5:     Dim dvx As Double = Me._vx - b._vx
   6:     Dim dvy As Double = Me._vy - b._vy
   7:     Dim distance2 As Double = dx * dx + dy * dy
   8:  
   9:     If Math.Abs(dx) > Me._d OrElse Math.Abs(dy) > Me._d Then
  10:         Return False
  11:     End If
  12:     If distance2 > Me._d2 Then
  13:         Return False
  14:     End If
  15:  
  16:     ' make absolutely elastic collision
  17:     Dim mag As Double = dvx * dx + dvy * dy
  18:  
  19:     ' test that balls move towards each other    
  20:     If mag > 0 Then
  21:         Return False
  22:     End If
  23:  
  24:     mag /= distance2
  25:  
  26:     Dim delta_vx As Double = dx * mag
  27:     Dim delta_vy As Double = dy * mag
  28:  
  29:     Me._vx -= delta_vx
  30:     Me._vy -= delta_vy
  31:  
  32:     b._vx += delta_vx
  33:     b._vy += delta_vy
  34:  
  35:     Return True
  36: End Function

User Interaction 

While the GameLoop Storyboard does a good job at managing ball movement, I didn't think it was the best option for handling pool stick and q-ball control.  During times of user control (stick aiming and q-ball "ball in hand"), I chose to use mouse movement and click events to determine object position and angle.  I created my own enumeration to manage switching between ball movement and user control states.  The rest involves some simple statistics and Silverlight XAML transformations.

Pool Stick Aiming and Ball-In-Hand

During a scratch, the MouseMove event allows the affect of moving the q-ball for placement.  During aiming, it allows the user to rotate the poolstick around the q-ball.

C#

   1: void Page_MouseMove(object sender, MouseEventArgs e)
   2: {
   3:  
   4:     m_MousePoint = e.GetPosition(this);
   5:  
   6:     switch (m_ActionState)
   7:     {
   8:         case m_ActionStates.Scratch:
   9:             m_QBall.MoveAbsolute(m_MousePoint.X, m_MousePoint.Y);
  10:             break;
  11:         case m_ActionStates.Aiming:
  12:             m_PoolStick.Rotate(m_MousePoint.X, m_MousePoint.Y, m_QBall.BallCenterX, m_QBall.BallCenterY);
  13:             break;
  14:     }      
  15:     
  16: }

VB

   1: Private Sub Page_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
   2:  
   3:     m_MousePoint = e.GetPosition(Me)
   4:  
   5:     Select Case m_ActionState
   6:         Case m_ActionStates.Scratch
   7:             m_QBall.MoveAbsolute(m_MousePoint.X, m_MousePoint.Y)
   8:         Case m_ActionStates.Aiming
   9:             m_PoolStick.Rotate(m_MousePoint.X, m_MousePoint.Y, m_QBall.BallCenterX, m_QBall.BallCenterY)
  10:     End Select
  11:  
  12: End Sub
 
The RotateTransform Rocks

The Silverlight RotateTranform is very powerful in this case.  It allows me to simply rotate the pool stick object around a center point (the center of the q-ball) using a relative angle between the mouse point and the ball. 

C#

   1: public void Rotate (double mouseX, double mouseY, double ballX, double ballY)
   2: {
   3:     double vx = ballX - mouseX;
   4:     double vy = ballY - mouseY;
   5:     
   6:     radians = Math.Atan2(vy, vx);
   7:     double angle = radians * (180/Math.PI);
   8:     
   9:     rootCanvas.RenderTransform = new RotateTransform 
  10:     { 
  11:         CenterX = Model.stickBuffer, 
  12:         CenterY = Model.stickHeight/2, 
  13:         Angle = angle 
  14:     };
  15:  
  16: }

VB

   1: Public Sub Rotate(ByVal mouseX As Double, ByVal mouseY As Double, ByVal ballX As Double, ByVal ballY As Double)
   2:     Dim vx As Double = ballX - mouseX
   3:     Dim vy As Double = ballY - mouseY
   4:  
   5:     radians = Math.Atan2(vy, vx)
   6:     Dim angle As Double = radians * (180 / Math.PI)
   7:  
   8:     Dim transform As RotateTransform = New RotateTransform()
   9:     transform.CenterX = Model.stickBuffer
  10:     transform.CenterY = Model.stickHeight / 2
  11:     transform.Angle = angle
  12:  
  13:     rootCanvas.RenderTransform = transform
  14:  
  15: End Sub
 
Power Control and Q-Ball Placement

Based on the ActionState, the mouse up and mouse down events manage user interaction during power adjustment and q-ball placement.

C#

   1: void Page_MouseLeftButtonUp(object sender, MouseEventArgs e)
   2: {
   3:     if (this.m_ActionState == m_ActionStates.AdjustingPower)
   4:     {
   5:         m_QBall.Strike(m_PoolStick.power, m_PoolStick.radians);
   6:  
   7:         m_PoolStick.StopPowerMovement();
   8:         this.Children.Remove(m_PoolStick);
   9:         this.m_ActionState = m_ActionStates.BallsMoving;
  10:     }
  11: }
  12:  
  13: void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
  14: {
  15:     switch (m_ActionState)
  16:     {
  17:         case m_ActionStates.Aiming:
  18:             m_PoolStick.StartPowerMovement();
  19:             this.m_ActionState = m_ActionStates.AdjustingPower;
  20:             break;
  21:         case m_ActionStates.Scratch:
  22:             ResetPoolStick();
  23:             m_ActionState = m_ActionStates.Aiming;
  24:             break;
  25:     }
  26: }

VB

   1: Private Sub Page_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseEventArgs)
   2:     If Me.m_ActionState = m_ActionStates.AdjustingPower Then
   3:         m_QBall.Strike(m_PoolStick.power, m_PoolStick.radians)
   4:  
   5:         m_PoolStick.StopPowerMovement()
   6:         Me.Children.Remove(m_PoolStick)
   7:         Me.m_ActionState = m_ActionStates.BallsMoving
   8:     End If
   9: End Sub
  10:  
  11: Private Sub Page_MouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseEventArgs)
  12:     Select Case m_ActionState
  13:         Case m_ActionStates.Aiming
  14:             m_PoolStick.StartPowerMovement()
  15:             Me.m_ActionState = m_ActionStates.AdjustingPower
  16:         Case m_ActionStates.Scratch
  17:             ResetPoolStick()
  18:             m_ActionState = m_ActionStates.Aiming
  19:     End Select
  20: End Sub
 

Game State and Control

The final component to this application is the "business rules" behind the game.  A game of 8-Ball is governed by the results of each player's shot.  Whether that shot ends the game or whose shot it is next is based on what balls are sunk. 

I created another enumeration for ShotResults which defines each potential outcome: GoAgain, NextPlayer, Scratch, ScratchOnEight, PrematureEightBall, and Win. 

C#

   1: private void UpdateGameState(ShotResults results)
   2: {
   3:     string resultText = "";
   4:     
   5:     switch (results)
   6:     {
   7:         // update game states
   8:         case ShotResults.PrematureEightBall:
   9:         case ShotResults.ScratchOnEight:
  10:             resultText = ProcessGameEnd(false, results);
  11:             m_ActionState = m_ActionStates.GameOver;
  12:             break;
  13:         case ShotResults.NextPlayer:
  14:             resultText = "Player looses turn";
  15:             ChangeCurrentPlayer();
  16:             ResetPoolStick();
  17:             m_ActionState = m_ActionStates.Aiming;
  18:             break;
  19:         case ShotResults.GoAgain:
  20:             resultText = "Nice job. Go again.";
  21:             ResetPoolStick();
  22:             m_ActionState = m_ActionStates.Aiming;
  23:             break;
  24:         case ShotResults.Scratch:
  25:             resultText = "Scratch. Player looses turn.";
  26:             ChangeCurrentPlayer();
  27:             AddBall(m_QBall);
  28:             m_ActionState = m_ActionStates.Scratch;
  29:             break;
  30:         case ShotResults.Win:
  31:             resultText = ProcessGameEnd(true, results);
  32:             m_ActionState = m_ActionStates.GameOver;
  33:             break;
  34:     }
  35:  
  36:     sunkBalls.Clear();
  37:  
  38:     this.Text_Status.Text = resultText;
  39: }

VB

   1: Private Sub UpdateGameState(ByVal results As ShotResults)
   2:     Dim resultText As String = ""
   3:  
   4:     Select Case results
   5:         ' update game states
   6:         Case ShotResults.PrematureEightBall, ShotResults.ScratchOnEight
   7:             resultText = ProcessGameEnd(False, results)
   8:             m_ActionState = m_ActionStates.GameOver
   9:         Case ShotResults.NextPlayer
  10:             resultText = "Player looses turn"
  11:             ChangeCurrentPlayer()
  12:             ResetPoolStick()
  13:             m_ActionState = m_ActionStates.Aiming
  14:         Case ShotResults.GoAgain
  15:             resultText = "Nice job. Go again."
  16:             ResetPoolStick()
  17:             m_ActionState = m_ActionStates.Aiming
  18:         Case ShotResults.Scratch
  19:             resultText = "Scratch. Player looses turn."
  20:             ChangeCurrentPlayer()
  21:             AddBall(m_QBall)
  22:             m_ActionState = m_ActionStates.Scratch
  23:         Case ShotResults.Win
  24:             resultText = ProcessGameEnd(True, results)
  25:             m_ActionState = m_ActionStates.GameOver
  26:     End Select
  27:  
  28:     sunkBalls.Clear()
  29:  
  30:     Me.Text_Status.Text = resultText
  31: End Sub

Conclusion

This was a fun and educational exercise for me (always a good combination).  I learned enough about graphics design to at least fear it less, especially with the use of the Expression suite.  It also opened my perspective of Silverlight to understand how it will empower us to implement more rich interactive applications.  The most interesting part for me, however, was the change in perspective I had to take in implementing an "always on" game.  As simple as a game of 8-ball is, it definitely required an adjustment in thinking from the event-driven model of programming I'm accustomed to using in my every day work. 

If you have any questions about this solution or have suggestions for improvement, feel free to contact me at jpetersen@claritycon.com

Tags:

Follow the Discussion

  • SilverlightShow HighlightsSilverlight​Show Highlights

    Posted in SilverlightShow Highlights

  • simonsimon

    thanks, good toturial, thou I don't understand all of it, I learned some stuff Smiley

  • Aaran HeaveyAaran Heavey

    I was wondering (as i am very new to programming),

    if it would be possible to do this project using visual studio 2005, silverlight and the expression components as all of these i have only just acquired.

  • Clint RutkasClint I'm a "developer"

    This was a fun and educational exercise for me (always a good combination).  I learned enough about graphics design to at least fear it less, especially with the use of the Expression suite.  It also opened my perspective of Silverlight to understand how it will empower us to implement more rich interactive applications.  The most interesting part for me, however, was the change in perspective I had to take in implementing an "always on" game.  As simple as a game of 8-ball is, it definitely required an adjustment in thinking from the event-driven model of programming I'm accustomed to using in my every day work.  

  • Random PedantRandom Pedant

    "Player looses turn" should be "Player loses turn".

  • Andy BoydAndy Boyd

    I too am accustomed to linear, event driven business-oriented programming, mainly with databases and VB.  Nice to see I'm not the only square trying to broaden his horizons Wink

  • SteveSteve

    Have downloaded the alpa 1.1 on windows xp service pack 2 with Windows Explorer on multiple machines ( and have restarted ). Keeps saying that I need to download Silverlight.

  • JeanieJeanie

    That was pretty awesome! Can you help me with code for a simple game of tic-tac-toe or mabey tell me where I might find code for the game?

    Thank you very much.

    Jeanie

  • Clint RutkasClint I'm a "developer"

    A Tic tac toe game is pretty easy Jeanie if you think about it, you have a fixed win state that you can check after each turn.

    What would be really neat is if you used Ink to draw the X and O.  I think I have a new idea ...

  • Clint RutkasClint I'm a "developer"

    Steve:  You may need to replace the JS files with the newer silverlight 1.1 files.  I've run into this problem too.  That is why Silverlight 1.1 is deemed alpha (not production ready) and has a bunch of disclaimers, when it gets updated, it may break stuff since it is still that new.

  • Noticias externasNoticias externas

    Title says it all Smiley My plan is to create small walkthrough how you can create your own Silverlight

  • Janne Mattila&#39;s blogJanne Mattila's blog

    Title says it all Smiley My plan is to create small walkthrough how you can create your own Silverlight

  • Blog de Juan Peláez en Geeks.msBlog de Juan Peláez en Geeks.ms

    Uno de los Blogs a los que estoy suscrito es el de Janne Mattila quien siempre tiene cosas muy interesantes

  • DaveDave

    Very Interesting... good article

  • a-to-tha-ka-to-tha-k

    i am unable to open this silverlight_8ball.sln in VisualBasic2008ExpressEdition why?

  • Clint RutkasClint I'm a "developer"

    In this article, I will discuss how I have built RetroCommand, a Silverlight 2.0 Beta version of a classic

  • TimiTimi

    hi, thanks for explaining your program

    I have been bugging my head about how to implement ball collision but it seems your solution is a good one.

    I understand the method: 'doCollide(param)' but would love to know why you did what you did in that method i.e. can u please exlain the formula.

    cheers

  • Russian Coding 4 FunRussian Coding 4 Fun

    В этой статье я опишу версию классической аркадной игры RetroCommand, созданную мной с применением Silverlight

  • Clint RutkasClint I'm a "developer"

    @ge-force This was with SL 1.1 which is javascript only.  This example would have to be updated to Silverlight 3.0 or 4.0 to get on Visual Studio 2010.  That requires shifting it from JS to SL.

  • ge-forcege-force

    Is there any way to right this just with Expression Blend, Silverlight 3, and Visual Studio 2010 Professional?

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.