Removing geometries/spatials from a level when exiting it

Hi

A question: How to remove all spatials from my scene when exiting to main menu?

This is my ModelState, however, when I exit to main menu it looks fine - but when I create a new game - all spatials from the last game are still present in the scene. Am I doing something wrong here:

[java]package alpineterra.appstates;

import alpineterra.IModelFactory;
import alpineterra.Main;
import alpineterra.components.ModelType;
import alpineterra.components.Position;
import alpineterra.components.TimeProvider;
import com.jme3.app.Application;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.simsilica.es.Entity;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;
import com.simsilica.es.EntitySet;
import com.simsilica.lemur.event.BaseAppState;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**

  • Watches entities with Position and ModelType components

  • and creates/destroys Spatials as needed as well as moving

  • them to the appropriate locations.

  • Spatials are created with a ModelFactory callback object

  • that can be game specific.

  • @author Asser Fahrenholz
    */
    public class ModelState extends BaseAppState {

    static Logger log = LoggerFactory.getLogger( ModelState.class );

    private EntityData ed;
    private EntitySet entities;
    private TimeProvider time;
    private Map<EntityId, Spatial> models = new HashMap<>();
    private Node modelRoot;
    private IModelFactory factory;

    public ModelState( TimeProvider time, IModelFactory factory ) {
    this.time = time;
    this.factory = factory;
    }

    public Node getModelRoot() {
    return modelRoot;
    }

    public Spatial getSpatial( EntityId entity ) {
    // Make sure we are up to date
    refreshModels();
    return models.get(entity);
    }

    public Collection<Spatial> spatials() {
    return models.values();
    }

    protected Spatial createSpatial( Entity e ) {
    return factory.createModel(e);
    }

    protected void addModels( Set<Entity> set ) {

    for( Entity e : set ) {
        // See if we already have one
        Spatial s = models.get(e.getId());
        if( s != null ) {
            log.error("Model already exists for added entity:" + e);
            continue;
        }
    
        s = createSpatial(e);
        models.put(e.getId(), s);
        updateModelSpatial(e, s);
        modelRoot.attachChild(s);
    }
    

    }

    protected void removeModels( Set<Entity> set ) {

    for( Entity e : set ) {
        Spatial s = models.remove(e.getId());
        if( s == null ) {
            log.error("Model not found for removed entity:" + e);
            continue;
        }
        s.removeFromParent();
    }
    

    }

    protected void updateModelSpatial( Entity e, Spatial s ) {
    /Position p = e.get(Position.class);
    InterpolationControl ic = s.getControl(InterpolationControl.class);
    if( ic != null ) {
    ic.setTarget(p.getLocation(), p.getFacing(), p.getChangeTime(), p.getTime());
    } else {
    s.setLocalTranslation(p.getLocation());
    s.setLocalRotation(p.getFacing());
    }
    /

    Position p = e.get(Position.class);
    s.setLocalTranslation(p.getLocation());
    s.setLocalRotation(p.getFacing());
    

    }

    protected void updateModels( Set<Entity> set ) {

    for( Entity e : set ) {
        Spatial s = models.get(e.getId());
        if( s == null ) {
            log.error("Model not found for updated entity:" + e);
            continue;
        }
    
        updateModelSpatial(e, s);
    }
    

    }

    protected void refreshModels() {
    if( entities.applyChanges() ) {
    removeModels(entities.getRemovedEntities());
    addModels(entities.getAddedEntities());
    updateModels(entities.getChangedEntities());
    }
    }

    @Override
    protected void initialize( Application app ) {

    factory.setState(this);
    
    // Grab the set of entities we are interested in
    ed = getState(EntityDataState.class).getEntityData();
    entities = ed.getEntities(Position.class, ModelType.class);
    
    // Create a root for all of the models we create
    modelRoot = new Node("Model Root");
    

    }

    @Override
    protected void cleanup( Application app ) {
    entities.applyChanges();
    this.removeModels(entities);
    modelRoot.removeFromParent();
    // Release the entity set we grabbed previously
    entities.release();
    entities = null;
    }

    @Override
    protected void enable() {
    ((Main)getApplication()).getRootNode().attachChild(modelRoot);

    entities.applyChanges();
    addModels(entities);
    

    }

    @Override
    public void update( float tpf ) {
    refreshModels();
    }

    @Override
    protected void disable() {
    entities.applyChanges();
    removeModels(entities);
    modelRoot.removeFromParent();
    }
    }
    [/java]

Oh dang, it’s my entity set. It’s not reset when creating a new game.

No worries.

@asser.fahrenholz said: Oh dang, it's my entity set. It's not reset when creating a new game.

No worries.

:slight_smile:

I was going to ask if you still have entities left over. In which case your model state was doing exactly what it was supposed to do (just not what you wanted it to do). :slight_smile:

This modelState manage only visual object…so ok when you unload your level the model state is cleared but the entityData always has the entities in memory…
When you reload the level the new modelState check the entityData and obviously find these entities and so new spatials to recreate…
Try to add a debug message when you add a spatial in your scene.

You have to clear all the entities related to that level when you quit.

entityData.removeEntity(EntityId id) for all the entities you’ve created in that level.

Or rather, when exiting from a game to main menu

@asser.fahrenholz said: Or rather, when exiting from a game to main menu

Yeah same thing ^^. Missreading :stuck_out_tongue:

@haze, correct. My only issue is - when exiting from a game to main menu, is it best practice to disable the state or to detach it ?

Is cleanup called when detaching a state ?

@asser.fahrenholz said: @haze, correct. My only issue is - when exiting from a game to main menu, is it best practice to disable the state or to detach it ?

Is cleanup called when detaching a state ?

Yeah cleanup is called just before the state is detached.

I personally detach states when exit yes. Enable/disable is more for pause purposes.

@asser.fahrenholz said: @haze, correct. My only issue is - when exiting from a game to main menu, is it best practice to disable the state or to detach it ?

Is cleanup called when detaching a state ?

cleanup() is called when detaching a state.

It just depends really. Most of my states I leave attached and enable/disable… which is why Lemur’s BaseAppState (which I use exclusively now as my app state base class and will someday roll it into JME core) has enable()/disable() methods specifically… getting the initialize/enable state right can be kind of tricky. Actually, I just noticed you are already using it. Heheh.

Also note: it is unnecessary to put the same code in disable() and cleanup() because disable() will always be called as part of app state detachment… just like cleanup(). In your case, when you detach the state you will be doing the same stuff twice.