2
\$\begingroup\$

EDIT: I've added bits of my GameplayScreen class so that you can see how my ball and bats are initialized.

This sounds like a very n00b question, but bear with me here:

I'm trying to access the position of my bat (paddle) in my pong game and use it in my ball class. I'm doing this because I want a particle effect to go off at the point of contact where the ball hits the bat.

Each time the ball hits the bat, I receive an error stating that I haven't created an instance of the bat. I understand that I have to (or can use a static class), but I'm not sure of how to do so in this example.

I've included both my Bat and Ball classes.

public class Ball
{
     ....
    private bool hasHitBat;
    private AIBat aiBat;
    private Bat bat;
     ....

    /// <summary>
    /// Constructor for the ball
    /// </summary>
    public Ball(ContentManager contentManager, Vector2 ScreenSize)
    {
        moveSpeed = 15f;
        speed = 0;
        texture = contentManager.Load<Texture2D>(@"gfx/balls/redBall");
        direction = 0;
        size = new Rectangle(0, 0, texture.Width, texture.Height);
        resetPos = new Vector2(ScreenSize.X / 2, ScreenSize.Y / 2);
        position = resetPos;
        rand = new Random();
        isVisible = true;
        hasHitBat = false;

        // Everything to do with particles
        List<Texture2D> textures = new List<Texture2D>();
        textures.Add(contentManager.Load<Texture2D>(@"gfx/particle/circle"));
        textures.Add(contentManager.Load<Texture2D>(@"gfx/particle/star"));
        textures.Add(contentManager.Load<Texture2D>(@"gfx/particle/diamond"));
        particleEngine = new ParticleEngine(textures, new Vector2());
    }

    /// <summary>
    /// Checks for the collision between the bat and the ball. Sends ball in the appropriate
    /// direction
    /// </summary>
    public void BatHit(int block)
    {
        if (direction > Math.PI * 1.5f || direction < Math.PI * 0.5f)
        {
            hasHitBat = true;
            particleEngine.EmitterLocation = new Vector2(aiBat.Position.X, aiBat.Position.Y);
            switch (block)
            {
                case 1:
                    direction = MathHelper.ToRadians(200);
                    break;
                case 2:
                    direction = MathHelper.ToRadians(195);
                    break;
                ................
            }
        }
        else
        {
            hasHitBat = true;
            particleEngine.EmitterLocation = new Vector2(bat.Position.X, bat.Position.Y);
            switch (block)
            {
                case 1:
                    direction = MathHelper.ToRadians(310);
                    break;
                case 2:
                    direction = MathHelper.ToRadians(345);
                    break;
                ...............
            }
        }

        if (rand.Next(2) == 0)
        {
            direction += MathHelper.ToRadians(rand.Next(3));
        }
            else
        {
            direction -= MathHelper.ToRadians(rand.Next(3));
        }
        AudioManager.Instance.PlaySoundEffect("hit");
    }
 }

public class Bat
{

    /// <summary>
    /// Constructor for the bat
    /// </summary>
    public Bat(ContentManager contentManager, Vector2 screenSize, bool side)
    {
        moveSpeed = 7f;
        turbo = 15f;
        recharge = 100f;
        points = 0;
        interval = 5f;
        leftBat = contentManager.Load<Texture2D>(@"gfx/bats/batGrey");
        size = new Rectangle(0, 0, leftBat.Width, leftBat.Height);

        // True means left bat, false means right bat. 
        if (side) Position = new Vector2(30, screenSize.Y / 2 - size.Height / 2);
        else Position = new Vector2(screenSize.X - 30, screenSize.Y / 2 - size.Height / 2);
        yHeight = (int)screenSize.Y;
    }


    /// <summary>
    /// Sets thedefault starting position for the bats
    /// </summary>
    /// <param name="position"></param>
    public void SetPosition(Vector2 position)
    {
        if (position.Y < 0)
        {
            position.Y = 0;
        }
        if (position.Y > yHeight - size.Height)
        {
            position.Y = yHeight - size.Height;
        }
        this.Position = position;
    }


    /// <summary>
    /// Checks for the current position of the bat
    /// </summary>
    public Vector2 GetPosition()
    {
        return Position;
    }

    /// <summary>
    /// Updates the position of the AI bat, in order to track the ball
    /// </summary>
    /// <param name="ball"></param>
    public virtual void UpdatePosition(Ball ball)
    {
        size.X = (int)Position.X;
        size.Y = (int)Position.Y;
    }

    /// <summary>
    /// Draws the bats
    /// </summary>
    public virtual void Draw(SpriteBatch batch)
    {
        batch.Draw(leftBat, size, Color.White);
    }


    }
}

Class GameplayScreen

   {
               protected void Initialize()
    {
        ........
        ball = new Ball(contentManager, new Vector2(ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Width,
                             ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Height));

        SetUpSingle(); // Sets up a single player game
        ......
 }

    /// <summary>
    /// Sets up a single player game, specifically the bats
    /// </summary>
    private void SetUpSingle()
    {
        rightBat = new AIBat(contentManager, new Vector2(ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Width,
             ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Height), false);
        leftBat = new Bat(contentManager, new Vector2(ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Width,
            ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Height), true);
    }
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Belongs on StackOverflow. From the FAQ: "General programming questions more likely belong on Stack Overflow instead of here. A good rule of thumb is to ask yourself: Would a professional game developer give me a better/different/more specific answer to this question than other programmers?" \$\endgroup\$ Commented Jun 20, 2012 at 1:42
  • \$\begingroup\$ Generally I use the GameDev section, but upon your notice it seems that it would be better in the general programming questions. Thank you. \$\endgroup\$ Commented Jun 20, 2012 at 2:00
  • \$\begingroup\$ That's fine; game programming questions normally do belong here and the rest of your questions are right at home here. It's just that this one falls under being very general. :) \$\endgroup\$ Commented Jun 20, 2012 at 2:21
  • 1
    \$\begingroup\$ That's a lot of code for a simple question. You should really try to narrow that down in the future. (and you can practice on this question!) \$\endgroup\$
    – House
    Commented Jun 20, 2012 at 14:50
  • \$\begingroup\$ Will do! (working on it right now) \$\endgroup\$ Commented Jun 20, 2012 at 17:57

3 Answers 3

4
\$\begingroup\$

It sounds like you have your game working, which means you have an instance of bat and ball somewhere in your code (Game1.cs or its equivalent), I assume.

If you need your ball object to react to the bat object's properties, then you need the ball class to have a reference to the bat instance. You can do this in two ways:

1) Services.

2) Pass an object via a constructor, property, or method.

3) Do neither. Instead, handle your collision detection and particle effects outside of both of these classes. (recommended)

Here's an example of passing the already created bat object to the ball constructor, so that ball can then access bat's public properties and methods:

// Game1.cs

private Bat bat;
private Ball ball;

protected void LoadContent() {
    ...
    Bat bat = new Bat();          // create the bat
    Ball ball = new Ball( bat );  // create the ball, passing in the bat
    ...
}

Then in your Ball class:

// Ball.cs

private Bat bat;                  // this will hold the local reference to the bat

public Ball( Bat myBat ) {
    ...
    bat = myBat;                  // this assigns and instantiates the member bat
    ...                           // with myBat which was passed from the constructor
}

public override Update( GameTime gameTime ) {
    ...
    // now we can access the bat via the local private member, bat
    doSomethingWithBat( bat.Position );
    ...
}

Of course, I would recommend against having objects rely on each other. Instead, I would write another class or a few lines of code outside of those classes to detect the collision and play render your particle effect. Something like:

public override void Update( GameTime gameTime ) {
    ...
    if( Collision( ball, bat ) ) {
        ParticleManager.Play( Particle.BigBaddaBoom, ball.Position );
    }
    ...
}

The reason I would recommend this method is that it decouples your classes from each other making debugging simpler and your classes more re-usable. If you end up chaining your objects together, a simple modification of one class can have cascading effects on the rest of your code making what should be a simple change into a major code refactor.


Here's how you could modify your existing classes to pass your Bat object to your Ball object:

public class Ball
{
    ....
    private bool hasHitBat;
    private AIBat aiBat;
    private Bat bat;    // this will hold the reference to the player's bat
                        // that can be used throughout the rest of the class
    ....

    /// <summary>
    /// Constructor for the ball
    /// </summary>
    public Ball(ContentManager contentManager, Vector2 ScreenSize, Bat playerBat ) {
        bat = playerBat;
        ...
    }

Then in your GameplayScreen, you would have to flip some things around:

class GameplayScreen 
{

    private Ball ball;
    private Bat leftBat;

    protected void Initialize()
    {
        ...
        SetUpSingle(); // Sets up a single player game

        ball = new Ball(
                   contentManager, 
                   new Vector2(
                       ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Width,
                       ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea.Height),
                   leftBat, );
        ...
    }

Now as your GameplayScreen updates the leftBat object, you'll need to update the Ball object (to let it know about the changes to it's local bat object), and your ball will be able to react to the player's bat.

I still think it's easier to test for collisions inside your GameplayScreen.Update() method, but if you want to go down this route, that would be how you could do it.

\$\endgroup\$
7
  • \$\begingroup\$ Thank you very much! VERY clear and concise answer. Yes, the ball and bat class are bot working correctly in my Game1 class. I'll take your advice and create another class just for collision (instead of relying on the ball class to do so). \$\endgroup\$ Commented Jun 20, 2012 at 10:47
  • \$\begingroup\$ Hmmm, using the "public Ball( Bat myBat )" way doesn't seem to be working for me. Am I supposed to create a new constructor for Ball, using just Bat myBat as a parameter? If I try passing that as a parameter into my current ball constructor I receive an error in my GameplayScreen class (where all of the logic for the screen occurs), because I cannot pass in Bat myBat into the new instance of Ball there. \$\endgroup\$ Commented Jun 20, 2012 at 17:54
  • \$\begingroup\$ Are your bat and ball instances both being created within the same scope? Also, looking at your snippet for your GameplayScreen, you are creating your bat objects after your ball. It needs to be the other way around, since your new ball class relies on a bat object to be created. \$\endgroup\$
    – Cypher
    Commented Jun 20, 2012 at 19:51
  • \$\begingroup\$ To answer your question, my examples were very basic to show you how to pass an object around. If you're going to go with that method, you'll need to add a parameter to your existing Ball constructor that takes in type Bat in addition to your ContentManager and Vector2 parameters. Then you can build your ball the same as you do now with the addition of an already instantiated bat object. Does that make more sense? \$\endgroup\$
    – Cypher
    Commented Jun 20, 2012 at 19:54
  • \$\begingroup\$ I've edited my answer to include some more specific examples. \$\endgroup\$
    – Cypher
    Commented Jun 20, 2012 at 20:11
3
\$\begingroup\$

I would remove the bat from your Ball class and change the BatHit method signature from:

public void BatHit(int block)

to

public void Hit(Bat bat, int block)

Your code then becomes

Bat bat = new Bat();
Ball ball = new Ball();
if (ball.Hit(bat))
{
    // Do something
}
\$\endgroup\$
1
\$\begingroup\$

It looks to me that you're not initializing bat or aiBat anywhere, which is what it's complaining about. Since they're defined as members of Ball, its going to be looking to those even if you have Bat and AIBat classes by those same names in another class.

\$\endgroup\$
3
  • \$\begingroup\$ Yes I figured as much, with not initializing it, and generally I just add something like "Bat bat = new Bat(INSERT PARAM FOR BAT);" But where would I initialize that in the Ball class? I wouldn't do it within the Ball constructor, would I? \$\endgroup\$ Commented Jun 20, 2012 at 1:34
  • \$\begingroup\$ I would personally initialize it outside of the ball class (probably in the same area where you initialize ball), then use a Service to get a reference to the paddle when you call your ball's constructor. The only problem with this method is that it means your bat must be initialized prior to initializing your ball. \$\endgroup\$
    – SomeGuy
    Commented Jun 20, 2012 at 1:39
  • \$\begingroup\$ lets see if I can add the comment right this time yes staying close to the code as possible, you would initiate it in the Ball class \$\endgroup\$ Commented Jun 20, 2012 at 1:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .