NoobHelp request - Changing level/stage - and - What code in what class?

So… I am aware “java intermediate” is a ‘requirement’ for using jme ((somewhat) properly)

I’m currently looking at design patterns / nearing what I’m hoping to be the end of it - so a lot of this may be solved by a soon-coming eureka moment. In the meantime tho…

I had this thought:

JMonkeh tutorials are pretty decent and get you almost entirely up and running just after doing those 8 tutorials

What I am missing after this, is

  • HOW DO I MOVE FROM LEVEL 1 to LEVEL 2 ??! appstates and controls? – I do not know, it is nowhere near obvious enough to me (I may be blind, yes…)
  • How should I split the code up into classes, as to make the game not load everything all the time?
  • How should I split the code up into classes, in general?

These 2(½) questions have been probably the only questions I have had since I started looking at JME like 1-2 years ago
And I’ve been trying to solve them by following weird youtube tutorials etc.

To some extent it saddens me that there is no info on these simple things to be found - if it was part of the standard tutorial I believe a heck of a lot of people would get much farther, much faster - and seeing as jme is pretty kickass, it could also result in a lot of the shitty games that would’ve been released in the future, won’t be shitty, cuz people have cool tools :stuck_out_tongue:

OH! and YO lol !! – When you try searching the site for anything with “level” or “stage” … you get bleep like volume and other “wrong” data.

In my opinion you would use AppStates, controls are for spatials (e.g a mob on spiders will have a control) but for levels use appstates and they would be their own class. Here is an example:

Main class
[java]
public class Main extends SimpleApplication {
public static void main(String[] args)
{
Main app = new Main();
app.start();
}
simpleInitApp()
{
stateManager.attach(new Level1());
}
}
[/java]
Then would have a class called level1:

[java]
public class Level1 extends AbstractAppState{

private SimpleApplication app;
private AssetManager assetManager;
private AppStateManager stateManager;
private AppSettings gameSettings;

@Override
public void initialize(AppStateManager stateManager, Application app) {
    super.initialize(stateManager, app);
    this.app = (SimpleApplication) app;
    this.assetManager = app.getAssetManager();



    //Do some code here, initialize level,npcs and player

}
}

[/java]

Then say if you kill a certain npc or reach a certain location in a level you could stateManager.attach(new Level2()); then continue on.

As for the negative aspects you are saying about jMonkey, remember this is a free engine they dont profit off this at all (so i believe). This is the strongest engine I have seen for Java (maybe runeTek is better but thats proprietary), but in terms of users its much smaller than Unreal or Unity. The community is growing, but these developers for this engine are quite small currently.

The reason you haven’t found anything on your questions is because those are things that would be highly specific to each individual project. There is no “level handler” built in to the engine per se, but there are the components built in to help you implement your own.

One way to make different levels is to create XML files with your level data, and use those in the process of building your level. The way I have been doing it is to create XML files that are created in a separate program I wrote, that are then serialized using jME’s built in “savable” class.

If you haven’t read:
Application States
Custom Controls

I would suggest that you do. These are two jME-specific class types that help you divide up your code in a reasonable way.

If you have read those and you still don’t quite know what’s going on, a more specific question tends to be more helpful on these forums, as users tend to not like to rehash material that exists elsewhere in the documentation.

Jakob -
that new level1(); I’ve been messing around with in my head for oh so long… I love you … :stuck_out_tongue:

Regarding negativity - I wasn’t trying to be negative in the negative sense… (erm :b) … While I agree jme team seems “small” and all that, I do have a lot of respect and admiration for them - but I will never let respect and admiration stand in the way of pointing out stuff I think could be improved.

When we/people point out stuff that we wish improved, and then go “come on! why arent you doing what I told you to!!” - that bleep’s wrong…
versus “this and that would make the world of a difference” … Altho the latter one might need a disclaimer, noting one is not saying “becuz it makes a world of a difference, you should… DO IT NOW!!” … :slight_smile: :slight_smile:

Thanks a bundle for answer =)

Scream -

“The reason you haven’t found anything on your questions is because those are things that would be highly specific to each individual project. There is no “level handler” built in to the engine per se, but there are the components built in to help you implement your own.”

This much I’ve understood to a great degree.

Nevertheless, some examples of some structures / ways of doing it, still helps millions and millions of worlds more than
“it’s project dependent, good luck”

There are bad questions, but there sure as hell are bad answers as well.

AppStates and Controls - I’m reading them now and then as I progress through the java tutorials Im doing on youtube. It’s vaguely becoming meaningful - actual use cases would be vastly more useful, granted they’re not stripped down oversimplified useless usecases.

Games almost always have levels. If I was to make a game engine with tutorials, I cannot think anything other than I’d include info on “common ways of changing level”

Thanks a lot for the answer.

Here’s an NPC from my game, this is the control class:
[java]public class RegCreepControl extends AbstractControl {

protected EnemyState EnemyState;
protected int creepNum;
public AStarPathFinder pathFinder;
private float updateTimer = 0;
public Path path;
public Vector3f baseVec;
private MoveCreep mc;
private DecimalFormat numFormatter = new DecimalFormat("0.0");

public RegCreepControl(EnemyState _state, AStarPathFinder pathFinder) {
    EnemyState = _state;
    this.pathFinder = pathFinder;
    this.baseVec = EnemyState.getBaseVec();
    this.mc = new MoveCreep(this);
}

public RegCreepControl() {
    this.mc = new MoveCreep(this);
}

@Override
protected void controlUpdate(float tpf) {
    if (EnemyState.isEnabled()) {
        if (updateTimer > .05) {
            mc.run();
            updateTimer = 0;
        } else {
            updateTimer += tpf;
        }
    }
}

public Graph getWorldGraph() {
    return EnemyState.getWorldGraph();
}

public String getFormattedCoords() {
    return formatRoundNumber(getX()) + "," + formatRoundNumber(getY());
}

public String formatRoundNumber(float f) {
    return numFormatter.format(Math.round(f));
}

public float getX() {
    return spatial.getWorldTranslation().x;
}

public float getY() {
    return spatial.getWorldTranslation().y;
}

public float getCreepSpeed() {
    return spatial.getUserData("Speed");
}

public int getCreepHealth() {
    return spatial.getUserData("Health");
}

/**
 * Decrements creep health, removes creep from world if below 0.
 *
 * @param damage
 * @return
 */
public int decCreepHealth(int damage) {
    int health = getCreepHealth();
    spatial.setUserData("Health", health - damage);
    if (health - damage <= 0) {
        removeCreep(true);
    }
    return health - damage;
}

public int getValue() {
    return spatial.getUserData("Value");
}

public Vector3f getCreepLocation() {
    return spatial.getLocalTranslation();
}

public void removeCreep(boolean wasKilled) {
    EnemyState.creepList.remove(spatial);
    if (wasKilled) {
        EnemyState.incPlrBudget(getValue());
        EnemyState.incPlrScore(getValue());
    } else {
        EnemyState.decPlrHealth(getValue());
    }
    spatial.removeFromParent();
    spatial.removeControl(this);
    mc = null;
    pathFinder = null;
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}

@Override
public RegCreepControl cloneForSpatial(Spatial spatial) {
    RegCreepControl control = new RegCreepControl(EnemyState, pathFinder);
    control.setSpatial(spatial);
    return control;
}

}[/java]

Here’s the AppState that it uses:
[java] public EnemyState() {}

@Override
public void initialize(AppStateManager stateManager, Application app) {
    super.initialize(stateManager, app);
    this.app = (SimpleApplication) app;
    this.stateManager = this.app.getStateManager();
    this.assetManager = this.app.getAssetManager();
    this.GameState = this.stateManager.getState(GameState.class);
    this.FriendlyState = this.stateManager.getState(FriendlyState.class);
    this.GraphicsState = this.stateManager.getState(GraphicsState.class);
    this.PathState = this.stateManager.getState(PathfindingState.class);
    this.rootNode = this.app.getRootNode();
    initFactories();
}

private void initFactories() {
    cf = new RegCreepFactory(GraphicsState, this);
    gf = new GlobFactory(this);
    rf = new RangerFactory(this);
}

public void initLists(boolean isProfile) {
    creepList = new ArrayList<Spatial>();
    globList = new ArrayList<Spatial>();
    if (isProfile) {
        random = new Random(42);
    } else {
        random = new Random();
    }        
    nextrandom = random.nextInt(50);
}

public void setEnemyParams(HashMap creepParams) {
    this.creepTypes = creepParams.keySet().toArray();
    this.creepParams = creepParams;
    attachCreepNode();
}

public String[] getCreepTypes() {
    return (String[]) creepTypes;
}

public void setCreepSpawnerList(ArrayList<Spatial> creepSpawnerList) {
    this.creepSpawnerList = creepSpawnerList;
}

@Override
public void update(float tpf) {
    if (isEnabled()) {
        if (randomCheck > nextrandom) {
            spawnRandomEnemy();
            getNextRandomSpecialEnemyInt();
            randomCheck = 0;
        } else {
             randomCheck += tpf;
        }
    }

}

/**
 * Determines how many seconds have to pass before another random enemy (non
 * standard creep) is spawned. TODO: Implement better system for random
 * enemies, perhaps XML.
 */
private void getNextRandomSpecialEnemyInt() {
    nextrandom = random.nextInt(50);
}

private void spawnRandomEnemy() {
    spawnGlob();
}

/**
 * Spawns a glob. Checks to see if there is already a glob on the tower that
 * was randomly selected, and if so, calls it's self again to find a new
 * tower.
 */
private void spawnGlob() {
    int towerVictimIndex = random.nextInt(FriendlyState.getTowerList().size());
    if (!FriendlyState.getTowerList().get(towerVictimIndex).getControl(TowerControl.class).getIsGlobbed()) {
        Vector3f towerVictimLocation = FriendlyState.getTowerList().get(towerVictimIndex).getLocalTranslation();
        Spatial glob = gf.getGlob(towerVictimLocation, towerVictimIndex);
        FriendlyState.getTowerList().get(towerVictimIndex).getControl(TowerControl.class).globTower();
        creepNode.attachChild(glob);
        globList.add(glob);
    } else {
        spawnGlob();
    }
}

public Sphere getGlobSphere() {
    return glob_sphere;
}

private void spawnRanger() {
    int towerVictimIndex = random.nextInt(FriendlyState.getTowerList().size());
    Spatial ranger = rf.getRanger(getRangerSpawnPoint(towerVictimIndex),
            towerVictimIndex);
    rangerList.add(ranger);
    creepNode.attachChild(ranger);
}

public Geometry getRangerGeom() {
    return new Geometry("Ranger", new Box(1, 1, 1));
}

/**
 * TODO: Algorithm for determining where rangers should spawn.
 *
 * @param towerVictimIndex
 * @return
 */
private Vector3f getRangerSpawnPoint(int towerVictimIndex) {
    return new Vector3f();
}

public void attachCreepNode() {
    rootNode.attachChild(creepNode);
}

public void spawnRegCreep(Vector3f spawnpoint) {
    creepList.add(cf.getCreep(spawnpoint, getNextCreepParams()));
    creepNode.attachChild(creepList.get(creepList.size() - 1));
}

public CreepParams getNextCreepParams() {
    return (CreepParams) creepParams.get(getNextCreepType());
}

public String getNextCreepType() {
    return (String) creepTypes[random.nextInt(creepTypes.length)];
}

public String getMatDir() {
    return GraphicsState.getMatDir();
}

public String getCreepMatLoc(String type) {
    return "Materials/" + getMatDir() + "/" + type + "Creep.j3m";
}

/**
 * Determines how many creeps should be on the map based upon the player's
 * level. The numbers are determined by nodes in the level XML file.
 *
 */
public int getNumCreepsByLevel() {
    return GameState.getNumCreeps();
}

public ArrayList<Spatial> getCreepSpawnerList() {
    return creepSpawnerList;
}

public Vector3f getBaseVec() {
    return GameState.getBaseVec();
}

public BoundingVolume getBaseBounds() {
    return GameState.getBaseBounds();
}

public Node getCreepNode() {
    return creepNode;
}

public ArrayList<Spatial> getCreepList() {
    return creepList;
}

public int getCreepListSize() {
    return creepList.size();
}

public AssetManager getAssetManager() {
    return assetManager;
}

public ArrayList<Spatial> getTowerList() {
    return FriendlyState.getTowerList();
}

public boolean[] getGlobbedTowerList() {
    return FriendlyState.getGlobbedTowerList();
}

public ArrayList<Spatial> getGlobList() {
    return globList;
}

public Box getUnivBox() {
    return univ_box;
}

public void decPlrHealth(int dam) {
    GameState.decPlrHealth(dam);
}

public void incPlrBudget(int value) {
    GameState.incPlrBudget(value);
}

public void incPlrScore(int inc) {
    GameState.incPlrScore(inc);
}

public void goToNextSpawner() {
    if (creepSpawnerList.size() > 1) {
        if (nextspawner < creepSpawnerList.size() - 1) {

            nextspawner += 1;
        } else {
            nextspawner = 0;
        }
    } else {
        nextspawner = 0;
    }
}

public int getNextSpawner() {
    return nextspawner;
}

public ScheduledThreadPoolExecutor getEx() {
    return GameState.getEx();
}

public SimpleApplication getApp() {
    return app;
}

public void seedForProfile() {
    random = new Random(42);
    nextrandom = random.nextInt(10);
    randomCheck = random.nextInt(10);
}

public Graph getWorldGraph() {
    return PathState.getWorldGraph();
}

public String getFormattedBaseCoords() {
    return GameState.getFormattedBaseCoords();
}

@Override
public void cleanup() {
    super.cleanup();
    creepNode.detachAllChildren();
    globList.clear();
    creepList.clear();
}

} [/java]

So there’s a couple of use cases.

Here’s an example of one of my XML levels.
[java]<?xml version=“1.0” encoding=“UTF-8”?>
<level>
<gameplayparams>
<param id = “levelParams”>
<numCreeps>1</numCreeps>
<creepMod>1</creepMod>
<levelCap>5000</levelCap>
<levelMod>1</levelMod>
<profile>false</profile>
<tutorial>true</tutorial>
<allowedenemies>111100</allowedenemies>
</param>
<param id = “playerParams”>
<plrHealth>100</plrHealth>
<plrBudget>100</plrBudget>
<plrLevel>0</plrLevel>
<plrScore>0</plrScore>
</param>
<param id = “geometryParams”>
<floor>
<scale>8.6f, 8.6f, -0.1f</scale>
</floor>
<tower>
<builtSize>0.5f, 0.5f, 1.0f</builtSize>
<unbuiltSize>0.5f, 0.5f, .1f</unbuiltSize>
<builtSelected>0.6f, 0.6f, 1.5f</builtSelected>
<unbuiltSelected>0.7f, 0.7f, 2.5f</unbuiltSelected>
<beamwidth>6.0f</beamwidth>
</tower>
<base>
<baseVec>0.0f, -7.0f, 0.0f</baseVec>
<baseScale>1.6f, 1.6f, 1.2f</baseScale>
</base>
<creepspawner>
<horizontalscale>1.0f, 0.5f, 0.25f</horizontalscale>
<verticalscale>0.5f, 1.0f, 0.25f</verticalscale>
</creepspawner>
<camera>
<camLocation>0,0,20f</camLocation>
</camera>
</param>
<param id = “towerParams”>
<numTowers>1</numTowers>
<towerTypes>Unbuilt,Empty,1,2,3,4</towerTypes>
<tower id = “0”>
<vec>0.0f,3.0f,0.1f</vec>
<isStarter>false</isStarter>
</tower>
</param>
</gameplayparams>

&lt;graphicsparams&gt;
    &lt;param id = "filterParams"&gt; 
        &lt;bloom&gt;true&lt;/bloom&gt;
        &lt;downsampling&gt;2.0f&lt;/downsampling&gt;
        &lt;blurscale&gt;5.0f&lt;/blurscale&gt;
        &lt;exposurepower&gt;6.0f&lt;/exposurepower&gt;
        &lt;bloomintensity&gt;3.0f&lt;/bloomintensity&gt;
        &lt;glowmode&gt;GlowMode.SceneAndObjects&lt;/glowmode&gt;
    &lt;/param&gt;
    &lt;param id = "materialParams"&gt;
        &lt;bgcolor&gt;Black&lt;/bgcolor&gt;
        &lt;matdir&gt;Neon&lt;/matdir&gt;
    &lt;/param&gt;
&lt;/graphicsparams&gt;
&lt;enemyparams&gt;
    &lt;param id = "creepParams"&gt;
        &lt;creepTypes&gt;Small,Medium,Large,Giant&lt;/creepTypes&gt;
        &lt;SmallCreep&gt;
            &lt;health&gt;100&lt;/health&gt;
            &lt;speed&gt;0.85f&lt;/speed&gt;
            &lt;size&gt;0.125f, 0.125f, 0.20f&lt;/size&gt;
            &lt;value&gt;1&lt;/value&gt;
        &lt;/SmallCreep&gt;
        &lt;MediumCreep&gt;
            &lt;health&gt;200&lt;/health&gt;
            &lt;speed&gt;0.35f&lt;/speed&gt;
            &lt;size&gt;0.25f, 0.25f, 0.40f&lt;/size&gt;
            &lt;value&gt;2&lt;/value&gt;
        &lt;/MediumCreep&gt;
        &lt;LargeCreep&gt;
            &lt;health&gt;400&lt;/health&gt;
            &lt;speed&gt;0.25f&lt;/speed&gt;
            &lt;size&gt;0.4f, 0.4f, 0.6f&lt;/size&gt;
            &lt;value&gt;5&lt;/value&gt;
        &lt;/LargeCreep&gt;
        &lt;GiantCreep&gt;
            &lt;health&gt;800&lt;/health&gt;
            &lt;speed&gt;0.30f&lt;/speed&gt;
            &lt;size&gt;0.6f, 0.6f, 0.8f&lt;/size&gt;
            &lt;value&gt;10&lt;/value&gt;
        &lt;/GiantCreep&gt;
    &lt;/param&gt;
    &lt;param id = "creepSpawnerParams"&gt;
        &lt;numSpawners&gt;1&lt;/numSpawners&gt;
        &lt;creepSpawner id = "0"&gt;
            &lt;vec&gt;0.0f, 7.0f, 1.0f&lt;/vec&gt;
            &lt;orientation&gt;horizontal&lt;/orientation&gt;
        &lt;/creepSpawner&gt;
    &lt;/param&gt;
&lt;/enemyparams&gt;

</level>[/java]

You could write some XML parsing code to take that and turn it into a level. You’d use an application state to do that, and then you’d assign custom controls to the things like spawners, creeps, and towers.

One problem you may run into with using Jacob’s exact code is that when you instantiate a new Level1 AppState inside of attaching it to the statemanager, it’s harder to detach that state later as you don’t retain a pointer to it. Another way of doing this is:
[java]private Level level1;

level1 = new Level(1);
stateManager.attach(level1);
[/java]

This way, when you go to attach your second level:
[java]private Level level1, level2;

stateManager.detach(level1);
level2 = new Level(2);
stateManager.attach(level2);[/java]

Hopefully that is of some use to you. The problem is, if you don’t ask for something specific, there’s a good chance that you’re wasting people’s time as the code I have posted could be completely useless for you, and it’s hard to give specific answers to nonspecific questions.

My mind’s current state of affairs:

To change, or use, levels - an object representing the level is what is needed.
The object that represents the level, must contain all the info needed for that level to work
To make the level object, many other objects will most likely be needed
To make the level object, design patterns may be useful - depending on gametype, one pattern may triumph greatly over another pattern

Starting to note, that changing the controls on an appstate containing a level, may effectively give the impression of playing a new level…?

… This seems like I’m starting to blog here… dear diary, I’ll go away and think for myself :slight_smile:

@Screamconjoiner said: Hopefully that is of some use to you. The problem is, if you don't ask for something specific, there's a good chance that you're wasting people's time as the code I have posted could be completely useless for you, and it's hard to give specific answers to nonspecific questions.

Holy hell scream … Many huxxxes and lovzes you are king :slight_smile:

  • Give a range of / random cases when people are unspecific :stuck_out_tongue: I read the ‘how to get answers’ and while it may be correct I disagree with it majorly. Yes people need to learn to think about what they’re asking, but sometimes that is a major part of the problem with finding the answer to something - once you have a proper question, finding the answer tends to be quite easy. The ability to properly recognize what the problem is, is the problem, globally, basically, I guess…