Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Rotating Sprite Objects on DirectDraw Wrapper for C#

0.00/5 (No votes)
9 Dec 2004 1  
Evaluate C# on delivering more than 1000 animated sprites on fullscreen 1280x1024.

Sample Image

Introduction

This project's primary objective is to observe C#'s behavior, when delivering hundreds of vision enabled objects, to be drawn by the DirectDraw wrapper (DDW) for Managed Code.

If primary objective is achieved and C#/DDW evaluates to be a solid project foundry working environment, the project's direction goes to creating a game-like tile world, with hundreds of visible standing and moving objects.

Using the code

By design, all methods should return null on success, or in case of failure, the exception generated within.

Here is a list of class descriptions.

  • System.Windows.Forms.Form Form1

    Holds user window, translates user input to engine.

  • CWorld

    A world coordinated system equal to the screen resolution 1280x1024.

  • CEngine

    Maintains state on user requests, units initialization, main cycle while updating and drawing units and text.

  • CDevice

    Encapsulates DDW initialization. Display mode is 1280x1024x16 (Width x Height x Depth).

  • CSprite

    Sprite description, total frames on x and y and respective size, or in case of a single image within the bitmap, 1x1 obviously.

  • CSurface

    Holds space for front and back buffers, and bitmaps (CSprite). Respective description and color keying.

  • CFrameRate

    Count number of frames delivered each second.

  • CUnit

    Basic unit information including class pointers CSprite, CUnitManager and States (ACTION_BASIC enum).

As states may accumulate (in future use), unit might want to STAND but also ROTATE_LEFT. I decided the best approach was to use a BitArray with size equal to number of states in the ACTION_BASIC enum.

<span class="cs-keyword">public</span> <span class="cs-keyword">enum</span> ACTION_BASIC
{ 
    STOPPED=<span class="cs-literal">0</span>,
    STOPPING,
    ROTATE_LEFT,
    ROTATE_RIGTH,
    MOVE_FORWARD,
    MOVE_BACKWARD,
    RUN_FORWARD,
    RUN_BACKWARD,
    UNABLE_TO_MOVE,
    SLIDE_LEFT,
    SLIDE_RIGTH,
}
<span class="cs-keyword">public</span> BitArray m_ACTION_BASIC = <span class="cs-keyword">new</span> BitArray(<span class="cs-keyword">new</span> <span class="cs-keyword">bool</span>[<span class="cs-literal">11</span>] 
  {<span class="cs-keyword">true</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>,<span class="cs-keyword">false</span>});

Calls are made with very simple get/set methods:

<span class="cs-comment">//</span>
<span class="cs-keyword">if</span> (<span class="cs-keyword">this</span>.m_ACTION_BASIC.Get((<span class="cs-keyword">int</span>)ACTION_BASIC.ROTATE_LEFT))
    <span class="cs-keyword">this</span>.Angle += <span class="cs-keyword">this</span>.m_TurnVelocity / m_FPS;
<span class="cs-comment">//</span>
m_ACTION_BASIC.Set((<span class="cs-keyword">int</span>)ACTION_BASIC.STOPPED,<span class="cs-keyword">true</span>);

Most important call to DDW is Draw, an example is shown below with full explanation.

s.Draw(    <span class="cs-comment">// Surface to write to</span>
     <span class="cs-keyword">new</span> Rectangle(    <span class="cs-comment">// writeto: In this case the screen coordinates rectangle</span>
         (<span class="cs-keyword">int</span>)m_X,
         (<span class="cs-keyword">int</span>)m_Y,
         m_CSprite.m_FrameWidth,
         m_CSprite.m_FrameHeigth
         ),
     m_CSprite.m_Surface.m_Surface,    <span class="cs-comment">// Surface to read from </span>
     <span class="cs-keyword">new</span> Rectangle(    <span class="cs-comment">// readfrom: In this case the sprite coordinates rectangle</span>
         (m_Frame % m_CSprite.m_NFrameX)*m_CSprite.m_FrameWidth,
         (m_Frame / m_CSprite.m_NFrameX)*m_CSprite.m_FrameHeigth,
         m_CSprite.m_FrameWidth, 
         m_CSprite.m_FrameHeigth 
         ), 
     DrawFlags.KeySource|DrawFlags.Wait <span class="cs-comment">// drawing flags</span>
     );

Easy reading, readers say. Actually it's very very easy, the first rectangle holds the surface to write to s, and coordinated values (remember screen size equals world size, so m_X represents x coordinate of unit on screen, same goes for y). The width and height comes from sprite declaration when loading.

The second rectangle holds the surface to read from, surface from CSprite in use, and coordinated values. So, as in this version, only the blue frames (as seen below) are used. The class has to hold the exact frame (m_Frame) depending on the angle it has. So based on the frame width, the exact x for the whole bitmap seen below is calculated using the formula the reader sees in last code snippet. Same goes for y.

Sample Image

Bitmap file loaded and stored as m_CSprite.

CUnitManager

Responsible for initializing units. Holds all initialized units on a linked list named UNITLIST (declaration is seen below). Delivers CEngine commands to units.

<span class="cs-keyword">public</span> <span class="cs-keyword">class</span> UNITLIST
{
     <span class="cs-keyword">private</span> <span class="cs-keyword">object</span> pPrev;
     <span class="cs-keyword">private</span> <span class="cs-keyword">object</span> pNext;
     <span class="cs-keyword">private</span> CUnit pItem;

     <span class="cs-keyword">public</span> UNITLIST _PREV {<span class="cs-keyword">get</span>{<span class="cs-keyword">return</span> (UNITLIST)pPrev;}
                            <span class="cs-keyword">set</span>{pPrev = (UNITLIST)value;}}
     <span class="cs-keyword">public</span> UNITLIST _NEXT {<span class="cs-keyword">get</span>{<span class="cs-keyword">return</span> (UNITLIST)pNext;}
                            <span class="cs-keyword">set</span>{pNext = (UNITLIST)value;}}

     <span class="cs-keyword">public</span> CUnit _UNIT {<span class="cs-keyword">get</span>{<span class="cs-keyword">return</span> pItem;}<span class="cs-keyword">set</span>{pItem = value;}}
}

In the example method below, CEngine may use CUnitManager to draw each CUnit in surface s. The cycle starts with a header element pHead and moves down the list, down to the last element inserted, executing a Draw method on each unit.

<span class="cs-keyword">public</span> System.Exception Draw(Microsoft.DirectX.DirectDraw.Surface s)
{
     <span class="cs-keyword">try</span>
     {
         UNITLIST ul = pHead;
         <span class="cs-keyword">for</span> (<span class="cs-keyword">int</span> i = <span class="cs-literal">0</span>; i < m_Count; i++)
         {
             ul._UNIT.Draw(s);
             ul = ul._NEXT;
         }
         <span class="cs-keyword">return</span> <span class="cs-keyword">null</span>;
     }
     <span class="cs-keyword">catch</span>(Exception e)
     {
         <span class="cs-keyword">this</span>.m_Trace.T(<span class="cpp-string">"CUnitManager::Draw>"</span>+e);
         <span class="cs-keyword">return</span> e;
     }
}

CUtils

This class holds 2D math actually. Each unit position is characterized as a x,y,angle VECTOR structure. These methods have small use in the present version but they will be very important when, in next version, as example: units try to move from point p1 to mouse-clicked point p2. The decision to include them in the present version demonstrates the concern about the accuracy of these calculations and gives readers time to criticize. Go nuts // thank you.

DistancePercentageFromOrigin(VECTOR vInicial,VECTOR vFinal,VECTOR vCurrent)
<span class="cs-keyword">public</span> <span class="cs-keyword">double</span> DistancePercentageFromOrigin(VECTOR vInicial, 
                                  VECTOR vFinal, VECTOR vCurrent)
{
     <span class="cs-keyword">double</span> dInicial=Math.Sqrt(Math.Pow((vFinal.x-vInicial.x),<span class="cs-literal">2</span>) + 
                     Math.Pow((vFinal.y-vInicial.y),<span class="cs-literal">2</span>));
     <span class="cs-keyword">double</span> dCurrent=Math.Sqrt(Math.Pow((vFinal.x-vCurrent.x),<span class="cs-literal">2</span>) + 
                     Math.Pow((vFinal.y-vCurrent.y),<span class="cs-literal">2</span>));
     <span class="cs-keyword">if</span> (dInicial==<span class="cs-literal">0</span>)
         <span class="cs-keyword">return</span> <span class="cs-literal">0</span>;
     <span class="cs-keyword">return</span> dCurrent*<span class="cs-literal">100</span>/dInicial;
}
InternalProduct(VECTOR v1, VECTOR v2)
 <span class="cs-keyword">public</span> <span class="cs-keyword">double</span> InternalProduct(VECTOR v1, VECTOR v2)
{
     <span class="cs-keyword">return</span> v1.x*v2.x+v1.y*v2.y;
}
Modulus(VECTOR v1)
<span class="cs-keyword">public</span> <span class="cs-keyword">double</span> Modulus(VECTOR v1)
{
     <span class="cs-keyword">return</span> Math.Sqrt(Math.Pow(v1.x,<span class="cs-literal">2</span>)+Math.Pow(v1.y,<span class="cs-literal">2</span>));
}
AngleBetweenTwoVectorsWithSameOrigin(VECTOR v1, VECTOR v2)
<span class="cs-keyword">public</span> <span class="cs-keyword">double</span> AngleBetweenTwoVectorsWithSameOrigin(VECTOR v1, VECTOR v2)
{
     <span class="cs-keyword">double</span> m1=Modulus(v1)*Modulus(v2);
     <span class="cs-keyword">if</span> (m1==<span class="cs-literal">0</span>)
         <span class="cs-keyword">return</span> <span class="cs-literal">0</span>;
     <span class="cs-keyword">double</span> m2=InternalProduct(v1,v2)/m1;
     <span class="cs-keyword">return</span> Math.Acos(m2);
}
DecideLeftRigthOnAngle(VECTOR v1, double dAngle)
<span class="cs-keyword">public</span> <span class="cs-keyword">int</span> DecideLeftRigthOnAngle(VECTOR v1, <span class="cs-keyword">double</span> dAngle)
{
     <span class="cs-keyword">if</span> (v1.x*-<span class="cs-literal">1</span>*Math.Sin(dAngle)<=Math.Cos(dAngle)*v1.y)
         <span class="cs-keyword">return</span> -<span class="cs-literal">1</span>;
     <span class="cs-keyword">return</span> <span class="cs-literal">1</span>;
}

CTrace

As this is a full-screen application and there is no IDE, thus no debug; the project includes a text file debug engine.

Points of Interest

  • (--) The transparency model on the DDW wouldn't work with a white background bitmap - so the project had to go with the black background the reader will see under the name: spr_blackdrop.png.
  • (--) The CUnitManager was a bit of a problem because the C# compiler would not accept a self referencing structure. The CUnitManager class has to hold the structure because it needs to deliver actions to each CUnit visible on screen. The first workaround was creating assessors to each variable on the structure. That did not work. In the second workaround, this issue was declaring the structure as a class. That works fine.
  • (++) Microsoft promised 98% of normal blitting with DirectDraw and C++. This project experienced a much easier way to code. C# really leverages the code learning experience and it does deliver the promise. Up to around 1000 units, turning left and right at really great speeds - a normal game viewing, using Radeon 9600 and 2.8MHz P4. Because of application/screen size limitations (no new sprite may overlap others at creation time), and also in this version, the world window equals the screen. Putting more than around 1000 units on screen will be a test in the next version.
  • (++) The reader should understand at this point where the project is going, a Real Time Strategy application.

History

  • 2004/12 0.0 Initial version with fixed world size. No Artificial Intelligence (AI) Units. Unit user commands include simple object manipulation (Add, Rotate).
  • 2005/01 0.1 Create a World file loader, and have a screen window size different than world size. New World Objects (buildings/trees). Unit AI level 1 (More Basic Body Actions) and Unit interaction (Who do I see?).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here