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