Here is the visual part which loads the models, looks pretty the same I have for my Invader example
/*
* artificials.ch 2016
* all rights reserved by christian liesch<liesch@gmx.ch>
*/
package ch.artificials.rose;
import ch.artificials.rose.components.Model;
import ch.artificials.rose.components.Position;
import ch.artificials.rose.components.Type;
import ch.artificials.rose.components.Visible;
import ch.artificials.rose.models.ModelFactory;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
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 java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.slf4j.LoggerFactory;
public class VisualAppState extends AbstractAppState {
private final org.slf4j.Logger logger;
private final Map<EntityId, Spatial> models;
private SimpleApplication app;
private EntityData ed;
private EntitySet visibleEntities;
private ModelFactory modelFactory;
public VisualAppState() {
this.logger = LoggerFactory.getLogger(VisualAppState.class);
this.models = new HashMap<>();
}
@Override
public void initialize(AppStateManager stateManager, Application app) {
logger.info("Initialize visual");
super.initialize(stateManager, app);
this.app = (SimpleApplication) app;
ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();
visibleEntities = ed.getEntities(Visible.class, Model.class, Position.class);
modelFactory = new ModelFactory(this.app);
}
@Override
public void cleanup() {
logger.debug("Cleanup");
}
@Override
public void update(float tpf) {
if (visibleEntities.applyChanges()) {
removeVisibles(visibleEntities.getRemovedEntities());
addVisibles(visibleEntities.getAddedEntities());
updateVisibles(visibleEntities.getChangedEntities());
}
}
private void removeVisibles(Set<Entity> entities) {
entities.stream().forEach((e) -> {
removeVisible(e);
});
}
private void removeVisible(Entity e) {
Spatial s = models.remove(e.getId());
if (s == null) {
logger.warn("Model not found for removed entity:{0}", e);
} else {
s.removeFromParent();
}
}
private void addVisibles(Set<Entity> entities) {
entities.stream().forEach((e) -> {
addVisible(e);
});
}
private void addVisible(Entity e) {
Spatial s = models.get(e.getId());
if (s != null) {
logger.warn("Model already exists for added entity:{0}", e);
} else {
s = createVisual(e);
this.app.getRootNode().attachChild(s);
models.put(e.getId(), s);
updateVisible(e, s);
}
}
private void updateVisibles(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = models.get(e.getId());
if (s == null) {
logger.warn("Model not found for updated entity:{0}", e);
continue;
}
updateVisible(e, s);
}
}
private void updateVisible(Entity e, Spatial s) {
Position position = e.get(Position.class);
s.setLocalTranslation(position.getLocation());
s.setLocalRotation(position.getRotation());
}
public Spatial createVisual(Entity e) {
Model model = e.get(Model.class);
Spatial spatial = modelFactory.create(model.getName());
spatial.setName(e.getId().toString());
Type type = ed.getComponent(e.getId(), Type.class);
if (type != null && (type.getName().equals(Type.ITEM) || type.getName().equals(Type.DOOR))) {
spatial.setUserData("EntityId", e.getId().getId());
}
return spatial;
}
}
The main bit here is the createVisual (there are for sure better names
)
And my factory looks that way:
/*
* artificials.ch 2016
* all rights reserved by christian liesch<liesch@gmx.ch>
*/
package ch.artificials.rose.models;
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.slf4j.LoggerFactory;
/**
*
* @author cli
*/
public class ModelFactory {
private final SimpleApplication app;
private final org.slf4j.Logger logger;
public ModelFactory(SimpleApplication app) {
this.app = app;
this.logger = LoggerFactory.getLogger(ModelFactory.class);
}
public Spatial create(String name) {
Node model;
logger.debug("Create model " + name);
try {
logger.debug("Try \"ch.artificials.rose.models.\" + name");
Class<?> modelClass = Class.forName("ch.artificials.rose.models." + name);
logger.debug("Try to get create method");
Method createMethod = modelClass.getDeclaredMethod("create", SimpleApplication.class);
logger.debug("Try to call create method");
model = (Node) createMethod.invoke(modelClass, app);
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
logger.debug("Model " + name + "do not have a specific factory", ex);
model = (Node) app.getAssetManager().loadModel("Models/" + name.replace(".", "/") + ".j3o");
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
}
model.setName(name);
return model;
}
}
It tries to load the corresponding class, if there is any it excutes the create method, if not it does a regular load with the asset manager.
A existing factory class could be
/*
* artificials.ch 2016
* all rights reserved by christian liesch<liesch@gmx.ch>
*/
package ch.artificials.rose.models.basement;
import ch.artificials.rose.models.ModelFactory;
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author cli
*/
public class WhiteDoor {
private static final Logger logger = Logger.getLogger(ModelFactory.class.getName());
static public Node create(SimpleApplication app) {
logger.log(Level.FINE, "Load model \"WhiteDoor\" with specific factory");
Node model = (Node) app.getAssetManager().loadModel("Models/basement/WhiteDoor.j3o");
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
((Node)model.getChild(0)).getChild(0).move(0.7f, 0, 0.2f);
model.getChild(0).move(-0.7f, 0, -0.2f);
return model;
}
}
A door where I shift the inner node so I have a rotation point where I want it… That way I can decorate those stuff I want with what ever I want. In your case this would be the billboard control…
I guess there are better solutions, maybe with the scene composer, but for me this solution is tip top so far. What I do not like in the scene composer is if I change it in blender and re-import it to j3o all the stuff I did in the scene composer is gone.
Maybe you spoted the
spatial.setName(e.getId().toString());
above in the createVisual? I use this to find my model nodes with in my animation system. I have an Animation component where I store the action and if I should loop, not loop or cycle. Works also as wished. Maybe we can argue and say just have one System for Visuals and Animation, I don’t know I like to separate stuff if possible.
And in ES there is most often more than one way for a solution.