Accessing main class objects list

Started by
30 comments, last by Alberth 7 years, 1 month ago

This may be obvious, but I'm wondering, what is a good way to add an object to a main class list of objects?

I can think of two ways, but neither is good I feel.

You can make the list of objects static and so call it anywhere in the code base.
You can pass the list of objects to everything that needs it, which could be a lot.

An example of where this would be necessary would be say when a Player class responds to 'shooting' input and acts on a Bullet object. The Player would either have to already hold a Bullet object and manage it, updating and rendering it, or create the Bullet object and pass it to the main class objects list. Then the main class would take care of updating and rendering it. Simplistically speaking.

Any ideas? Am I missing something obvious?

Advertisement
The player shouldn't be responsible for what happens to the bullet once it has been fired, and the player shouldn't be responsible for the effect it will have in the world either.

The actual updating and rendering of the bullet can be done within the bullet class its self (but managed at a high level from the main class or "scene manager").

The hit detection should be handled by the scene manager too.

Basically what I'm trying to say is keep your objects separate from each other. It will make behaviors easier to manage and keep your code clearer.

If you have an object that needs to notify the scene of an event then there are a few ways of dealing with that without copying data everywhere.

One way would be for your scene objects to know about the scene manager so they can call some event function.

Another and cleaner way would be to have the scene manager register for object events. Then the objects just notify whoever is listening. This design helps to abstract things out and reduce dependencies.

Thanks very much Nyssa. Could you go into more detail about the last two paragraphs to show how such designs might be implemented? I am definitely interested in trying out what you've suggested.

Though most people will say that global access to anything will lead to bad design (and that is mostly always true), the other side of having absolutely no globals will also be very painful. That being said, only make global (or singleton, or service locator, or etc) if you truly need it to be - usually a few managers. If you have more than a dozen of globals, something might be seriously wrong with your design.

If you're new to programming, I'd advice to learn more about coding itself before game making. If you still want to make games as you learn, use a engine instead (Unity3d, for example). I did the mistake of trying to learn how to make an engine and a game when I was a newbie (even for simple games). The learning curve becomes just too slow.

Thanks very much Nyssa. Could you go into more detail about the last two paragraphs to show how such designs might be implemented? I am definitely interested in trying out what you've suggested.


Well my first example might look like in Java:

class SceneObject
{
    SceneManager manager_;
    
    public SceneObject(SceneManager manager)
    {
        manager_ = manager;
    }

    void update()
    {
        //...

        //something happended 
        manager_.onEvent(this);
    }
}

class SceneManager
{
    public void onEvent(SceneObject o)
    {
        //handle the event
    }
}
And my second example might look like:

interface EventCallback
{
    public void onEvent(SceneObject o);
}

class SceneObject
{
    ArrayList<EventCallback> listeners_ = new ArrayList<>();

    void addListener(EventCallback listener)
    {
        listeners_.add(listener);
    }

    void update()
    {
        //...

        //something happended
        for (EventCallback c : listeners_)
        {
            c.onEvent(this);
        }
    }
}

class SceneManager implements  EventCallback
{
    public void onEvent(SceneObject o)
    {
        //handle the event
    }
}

I have written a small LibGDX program attempting to implement the observer pattern to help with object decoupling. It needs work however and I would like some help with it. Here is the source code. My questions are below.


public class ObserverTest extends ApplicationAdapter implements Observer
{
	private SpriteBatch batch;
	private List<GameObject> gameObjects;
	public static int frameCount = 0;
	
	@Override
	public void create()
	{
		batch = new SpriteBatch();
		gameObjects = new ArrayList<GameObject>();

		gameObjects.add(new Ship(new Vector2(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2),
				new Vector2(50, 50), 5.0, 0.0, 5.0, Color.RED));

		// After all objects have been added
		for(GameObject gameObject : gameObjects)
		{
			if(gameObject instanceof Subject)
			{
				((Subject)gameObject).addObserver(this);
			}
		}
	}

	@Override
	public void render()
	{
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

		// Why does this not work when adding new objects while iterating?
		/*for(GameObject gameObject : gameObjects)
		{
			gameObject.update();
		}*/

		// Why does this work when adding new objects while iterating?
		for(int i = 0; i < gameObjects.size(); ++i)
		{
			gameObjects.get(i).update();
		}

		batch.begin();
		for(GameObject gameObject : gameObjects)
		{
			gameObject.render(batch);
		}
		batch.end();

		frameCount++;

                // Will this work as I expect it to?
		if(frameCount == Integer.MAX_VALUE)
		{
			frameCount = 0;
		}
	}

	@Override
	public void dispose()
	{
		batch.dispose();
	}

	@Override
	public void respond(GameObject gameObject, Event event)
	{
		switch(event)
		{
			case SHIP_FIRED_BULLET:
			{
				gameObjects.add(gameObject);
				break;
			}
		}
	}
}

public abstract class GameObject
{
    protected Vector2 position, dimension, direction;
    protected double speed, angle, rotationSpeed;
    protected Texture image;

    protected GameObject()
    {
        position = new Vector2(0, 0);
        dimension = new Vector2(0, 0);
        direction = new Vector2(0, 0);
        speed = 0.0;
        angle = 0.0;
        rotationSpeed = 0.0;
    }

    public abstract void update();
    public abstract void render(SpriteBatch batch);
}

public class Ship extends GameObject implements Subject
{
    private List<Observer> observers;

    public Ship(Vector2 position, Vector2 dimension, double speed, double angle, double rotationSpeed, Color color)
    {
        this.position = position;
        this.dimension = dimension;
        this.speed = speed;
        this.angle = angle;
        this.rotationSpeed = rotationSpeed;
        observers = new ArrayList<Observer>();

        Pixmap pixmap = new Pixmap((int)dimension.x, (int)dimension.y, Pixmap.Format.RGBA8888);
        pixmap.setColor(color);
        pixmap.drawLine(0, 0, (int)dimension.x, (int)dimension.y / 2); // Outside left
        pixmap.drawLine(0, (int)dimension.y, (int)dimension.x, (int)dimension.y / 2); // Outside right
        pixmap.drawLine(0, 0, (int)dimension.x / 2, (int)dimension.y / 2); // Inside left
        pixmap.drawLine(0, (int)dimension.y, (int)dimension.x / 2, (int)dimension.y / 2); // Inside right

        image = new Texture(pixmap);
        image.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); // Reduces jagged lines

        pixmap.dispose();
    }

    @Override
    public void update()
    {
        direction.x = (float)Math.cos(Math.toRadians(angle));
        direction.y = (float)Math.sin(Math.toRadians(angle));
        direction.nor();
        direction.scl((float)speed);

        if(Gdx.input.isKeyPressed(Input.Keys.UP))
        {
            position.add(direction);
        }

        if(Gdx.input.isKeyPressed(Input.Keys.LEFT))
        {
            angle += rotationSpeed;
        }

        if(Gdx.input.isKeyPressed(Input.Keys.RIGHT))
        {
            angle -= rotationSpeed;
        }

        if(Gdx.input.isKeyPressed(Input.Keys.M) && ObserverTest.frameCount % 10 == 0)
        {
            // How to avoid creating a new Bullet each time?
            Bullet bullet = new Bullet(new Vector2(position.x, position.y), new Vector2(10, 1),
                    dimension.x / 2, speed * 2, angle, Color.RED);

            for(Observer observer : observers)
            {
                observer.respond(bullet, Event.SHIP_FIRED_BULLET);
            }
        }
    }

    @Override
    public void render(SpriteBatch batch)
    {
        batch.draw(image,
                position.x - dimension.x / 2, position.y - dimension.y / 2,
                dimension.x / 2, dimension.y / 2, // Origin is center point for rotation
                dimension.x, dimension.y,
                1f, 1f,
                (float)angle,
                0, 0, (int)dimension.x, (int)dimension.y,
                false, false);
    }

    @Override
    public void addObserver(Observer observer)
    {
        observers.add(observer);
    }

    @Override
    public void removeObserver(int position)
    {
        observers.remove(position);
    }
}

public class Bullet extends GameObject
{
    public Bullet(Vector2 position, Vector2 dimension, double displacement, double speed, double angle, Color color)
    {
        this.position = position;
        this.dimension = dimension;
        this.speed = speed;
        this.angle = angle;

        direction.x = (float)Math.cos(Math.toRadians(angle));
        direction.y = (float)Math.sin(Math.toRadians(angle));
        direction.nor();
        direction.scl((float)speed);

        // This is just to ensure there isn't a divide by 0 error
        if(displacement != 0)
        {
            /*
                Here I'm trying to position the Bullet, half the width of its Ship away
                from its Ship (displacement), at a point determined by the Ships direction.

                As it is this works currently, but I don't know why. The 10.0f is arbitrary.
                If the Ships dimensions are changed, the position of the Bullet upon being
                fired can become incorrect.

                How can I set the Bullets initial position correctly?
            */
            position.mulAdd(direction, (float)displacement / 10.0f);
        }

        Pixmap pixmap = new Pixmap((int)dimension.x, (int)dimension.y, Pixmap.Format.RGBA8888);
        pixmap.setColor(color);
        pixmap.drawLine(0, 0, (int)dimension.x, 0);

        image = new Texture(pixmap);
        image.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); // Reduces jagged lines

        pixmap.dispose();
    }

    @Override
    public void update()
    {
        position.add(direction);
    }

    @Override
    public void render(SpriteBatch batch)
    {
        batch.draw(image,
                position.x - dimension.x / 2, position.y - dimension.y / 2,
                dimension.x / 2, dimension.y / 2, // Origin is center point for rotation
                dimension.x, dimension.y,
                1f, 1f,
                (float)angle,
                0, 0, (int)dimension.x, (int)dimension.y,
                false, false);
    }
}

public enum Event
{
    SHIP_FIRED_BULLET
}

public interface Observer
{
    void respond(GameObject gameObject, Event event);
}

public interface Subject
{
    void addObserver(Observer observer);
    void removeObserver(int position);
}

Regarding the code above, primarily I would like to know:

  1. Have I implemented the observer pattern correctly? Can it be improved and if so, how?
  2. Is my implementation scalable, as far as can be perceived currently?
  3. Is it okay to make Subject an interface? Every Subject is required to have a List of Observers, but enforcing this by making Subject a class which holds that List of Observers does not seem feasible to me. It would be difficult if not impossible to fit Subject into the GameObject class hierarchy in a correct manner, at least as far as I can see. As such, currently I must remember to give each Subject its own List of Observers independently. Is this reasonable or suitable?
  4. How can I avoid creating a new Bullet each time the Ship fires? The Bullet needs certain information from its firing Ship such as position and angle and currently this is most easily done by creating a new one upon firing and passing the information to the Bullet via its constructor. I do not think this is a good solution however. I would rather somehow re-use existing Bullets upon firing.
  5. Why does a standard for loop work when adding new objects to gameObjects in ObserverTest while iterating over it, while a for each loop does not? (Question in the code)
  6. Will frameCount be set back to 0 upon reaching MAX_VALUE? (Question in the code)
  7. How can I properly place the Bullet relative to the Ships position? (Question in the code)

Are there any other details I might have missed that should be examined? Help with this would be much appreciated. Thank you.

Any help? I don't know how to proceed at the moment. If there is anything that is not clear let me know and I'll try to clarify.

It's usually best not to pack a bunch of different and unrelated questions into one post, especially when it no longer matches the thread subject, as it tends to discourage people. As you've now found out. In future, start new threads for specific questions.

That being said:

1. No, the "for(Observer observer : observers)" loop should be handled by the base class, not written in everywhere you want to trigger an event. Usually there is a notify() function which will call the relevant handler (in your case, 'respond') on each observer.

2. Scalability is not a concern at this stage. Having understandable and maintainable code is.

3. Ideally the Subject is a class, as it would normally manage its own observer list. If you don't want to do that, or can't (for some other reason), it could be an interface, and each class could handle the notify loop itself. Personally I'd just ensure that one of the base classes handles this observable property and leave it at that.

4. When you delete Bullets, put them in a dead list. When you create bullets, see if you can recycle one from the dead list first. If not, create a new one like usual.

5. Generally speaking you should never add or remove to a collection while iterating over it, because the system that does the iteration can get confused. When you use the second syntax, you may appear to get away with it, but it's potentially dangerous. Maybe someone with more Java knowledge can give you a better answer.

6. Yes, probably, although you will never execute that code. At 60 frames per second it would take over a year of constant play to hit that value.

7. Have the firer calculate the initial position of the bullet and pass that in correctly.

5. Generally speaking you should never add or remove to a collection while iterating over it, because the system that does the iteration can get confused. When you use the second syntax, you may appear to get away with it, but it's potentially dangerous. Maybe someone with more Java knowledge can give you a better answer.

I agree with the idea that you should not add or remove items while iterating over the containing list. It's confusing at best, and problematic or lethal in other cases.

Extending on the answer, a List<> class doesn't store its elements in the class. It uses a Java array to store its elements:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/ArrayList.java#111

The ":" loop makes an iterator that walks over the element Java array directly, avoiding the List.get() method for each access. If you add new elements, at some point a new array is constructed, and set in the List class:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/ArrayList.java#213

Any already existing iterator however, still uses the previous Java array, unaware of the re-allocation that took place in the List class.

The "int i" loop is not iterating over the list in the technical sense of the word. You have an integer value that increases in value, and you access the List class a lot of times. That you constantly query the length of the list, and access each element in incrementing fashion is purely coincidental as far as Java is concerned. The important thing here is that you use the external interface of the List, so you ask elements through List.elementData each time.

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/ArrayList.java#384

If the internal Java array gets re-allocated, List.elementData changes too, and your next List.get uses the new Java array and not the old one.

However, this does not qualify as "working", in my opinion. You're lucky that List.add appends an element at the end. If you do List.insert and insert at the beginning, things will still break badly.

I was continuing on from Nyssas second example, which looked like the observer pattern to me. What I'm doing is still trying to find a good way to interact with the main class objects list, just now with the observer pattern. There are a few other things I've brought up which are unrelated to the original topic but only as a consequence of organically delving deeper into the topic and picking up on some things I had not thought of before, which are within the implementation of a design relating to the original topic.

Anyway, I don't want to get into a pointless argument over this. I appreciate where you're coming from Kylotan, so thank you for the advice.

Back to the topic, what I would really like is a detailed look at how I can correct my implementation, with explanation and code. Good general advice is always appreciated too. I know last year when I was looking for help with input, states and how to handle transitions between them Alberth was very obliging in helping me, and did so with a fantastic level of thought and detail. That thread too grew from simply being about input to something more complex. Perhaps it's a bit much to ask for the same with this, but it is at the moment what I think I need in order to gain a better understanding.

Your consideration would be appreciated.

This topic is closed to new replies.

Advertisement