Bots Animating With Player?

Started by Minigame, April 26, 2015, 08:04:19 AM

Previous topic - Next topic

Minigame

It's been quite a while since I've posted, or even programmed! So hello again jPCT community.

Anyways, I picked up a project that was inactive for quite some time and tonight I've implemented bots which are meant to wonder the game world and do random things... as expected.

So the problem is that all bots animate with the player? Example: When I walk, the bots stay idle but perform the movement animation. The bots are meant to be their own entities, and not do as the player does. I believe the problem has something to do with the method I store and clone the model...

Here is some code:

snippet from model storage class


public ObjectCache() {
this.male = generateMale();
this.female = generateFemale();
    }

private Object3D generateMale() {
Object3D male = Loader.loadMD2("./res/md2/male.md2", .10f / 2);
textureManager.addTexture("male", new Texture("./res/md2/male.jpg"));
textureManager.addTexture("male-mask", new Texture("./res/md2/male.png"));
this.maleBaseTexture = new TextureInfo(textureManager.getTextureID("male"));
maleBaseTexture.add(textureManager.getTextureID("male-mask"), TextureInfo.MODE_ADD);
male.setTexture(maleBaseTexture);
male.rotateY((float) Math.PI * 1.5f); // Properly rotate the MD2 model
male.rotateMesh();
male.compile(true); // Compile dynamic object
return male;
}

private Object3D generateFemale() {
Object3D female = Loader.loadMD2("./res/md2/female.md2", .091f / 2);
textureManager.addTexture("female", new Texture("./res/md2/female.jpg"));
textureManager.addTexture("female-mask", new Texture("./res/md2/female.png"));
this.femaleBaseTexture = new TextureInfo(textureManager.getTextureID("female"));
femaleBaseTexture.add(textureManager.getTextureID("female-mask"), TextureInfo.MODE_ADD);
female.setTexture(femaleBaseTexture);
female.rotateY((float) Math.PI * 1.5f); // Properly rotate the MD2 model
female.rotateMesh();
female.compile(true); // Compile dynamic object
return female;
}
        // note "male" and "female" are models which are loaded and storaged once.
public Object3D getMob(boolean gender, final float[][] colors) {
Object3D obj = new Object3D(gender ? male : female, true);
obj.shareCompiledData(gender ? male : female);
GLSLShader shader = new GLSLShader(Loader.loadTextFile("./res/md2/player.vert"), Loader.loadTextFile("./res/md2/player.frag")) {
@Override
public void setCurrentObject3D(Object3D obj) {
setUniform("colorMul0", colors[0]);
setUniform("colorMul1", colors[1]);
setUniform("colorMul2", colors[2]);
setStaticUniform("map0", 0);
setStaticUniform("map1", 1);
}
};
obj.setRenderHook(shader);
return obj;
}



Player.java

public class Player {

private final World world;

// the model which represents our player
private Object3D model;

// position stuff
private SimpleVector cachedPosition = new SimpleVector(0, 0, 0);
private SimpleVector nextPosition = null;

// animation stuff
private float animationFrame = 0;
private int animationId = 1;

// name above head stuff
private String username = "Player Name";
private SimpleVector aboveHeadVector;
private int nameLength = 0;

// path finder stuff
private PathFinder pathFinder;
private Path path;
private long lastAutoWalTranslation = 0L;
private int currentStepIndex = 0;
private int pathTranslations = 0;
private Tile targetTile;

public Player(final World world, Terrain terrain) {
this.world = world;
this.pathFinder = new AStarPathFinder(terrain, 64 * 64, true);
}

public void setUsername(GLFont font, String username) {
this.username = username;
nameLength = font.getStringBounds(username).width / 2;
}

public void update() {
autoWalk(); // process auto walk
translate(); // do smooth translations
animate(); // animate
}

/**
* Translates the model in 3D space.
*/
public void translate(boolean smooth, float x, float y, float z) {
if (!smooth) {
/*
* Teleport the player to the destination.
*/
this.model.clearTranslation();
this.model.translate(x, y, z);
this.cachedPosition = model.getTransformedCenter();
resetPath();
} else {
/*
* Set the next position variable so that we can begin the auto walk
* process using a series of smaller, "smooth", translations.
*/
this.nextPosition = new SimpleVector(x, y, z);
}
}

private void translate() {
if (nextPosition != null) {
/*
* Check if we're within distance of the target destination.
*/
if (MathUtil.inRange(1.5f, false, cachedPosition, nextPosition)) {
/*
* If so, let's reset the nextPosition variable so that the player
* stops auto walking.
*/
nextPosition = null;
return;
}
/*
* Rotate towards next position.
*/
SimpleVector s = new SimpleVector(getX(), 0, getZ());
SimpleVector t = new SimpleVector(nextPosition.x, 0, nextPosition.z);
SimpleVector direction = new SimpleVector(t.calcSub(s)).normalize();
Matrix rotationMatrix = new Matrix(direction.getRotationMatrix());
model.setRotationMatrix(rotationMatrix);

/*
* Translate forward towards the destination.
*/
SimpleVector forward = model.getZAxis();
forward.scalarMul(.36f); // speed
SimpleVector moveVector = new SimpleVector(forward);
model.translate(moveVector);
this.cachedPosition = model.getTransformedCenter();
}
}

/**
* Here we're just using jPCT's md2 animation system.
*/
private void animate() {
animationFrame += 0.02f;
if (animationFrame > 1) {
animationFrame -= 1;
}
animationId = path != null ? 2 : 1; // walking or idle
if (model != null) {
model.animate(animationFrame, animationId);
}
}

/**
* Builds a new model instance.
*/
public void build(boolean male, float[][] colors) {
if (model != null) {
cachedPosition = model.getTransformedCenter();
world.removeObject(model);
model = null;
}
this.model = ObjectCache.getInstance().getMob(male, colors);
model.build();
model.compile(true);
world.addObject(model);
model.translate(cachedPosition);
}

/**
* Renders the users name above their head.
* @note: This MUST be called after rendering the jPCT world.
*/
public void renderAboveHeadText(GLFont font, Camera camera, FrameBuffer frameBuffer) {
try {
aboveHeadVector = Interact2D.project3D2D(camera, frameBuffer, model.getTransformedCenter());
if (aboveHeadVector != null && username != "" && username.length() > 0) {
font.drawString(frameBuffer, username, (int) aboveHeadVector.x - nameLength, (int) aboveHeadVector.y - 42, Color.WHITE);
}
} catch (Exception ignore) {
// This happens because the vector returned null - and cannot be avoided.
// Just ignore it.
}
}

/**
* Generates a new A* path to the supplied destination.
*/
public void generatePath(int snapped_x, int snapped_z) {
if (path != null) {
resetPath();
}
path = pathFinder.findPath(model, getX(), getZ(), snapped_x, snapped_z);
if (path == null) {
System.err.println("Cannot generate path!");
return;
}
currentStepIndex = 0;
pathTranslations = path.getLength();
setTargetTile(snapped_x, snapped_z);
}

/**
* Resets all of the auto walk related variables.
*/
private void resetPath() {
nextPosition = null;
path = null;
currentStepIndex = 0;
pathTranslations = 0;
}

/**
* If the player has a path to follow, let's loop through
* the steps and set the smooth translation request.
*/
private void autoWalk() {
if (path != null && lastAutoWalTranslation < (System.nanoTime() / 1000000) - 35) {
lastAutoWalTranslation = System.nanoTime() / 1000000;
currentStepIndex++;
if (currentStepIndex == pathTranslations) {
resetPath();
return;
}
translate(true, path.getX(currentStepIndex), 0, path.getY(currentStepIndex));
//setTargetTile(path.getX(currentStepIndex), path.getY(currentStepIndex));
}
}

private void setTargetTile(int x, int z) {
// update target tile
if (targetTile != null) {
world.removeObject(targetTile);
}
int targetX = x / 6;
int targetZ = z / 6;
targetTile = new Tile(targetX * 6, targetZ * 6);
targetTile.translate(0, -1, 0);
targetTile.setAdditionalColor(Color.YELLOW);
world.addObject(targetTile);
}

/**
* Returns the players current position.
*/
public SimpleVector getTransformedCenter() {
return model == null ? cachedPosition : model.getTransformedCenter();
}

/**
* Returns the model instance.
*/
public Object3D getModel() {
return model;
}

public Object3D getTargetTile() {
return targetTile;
}

/**
* Returns the players X coordinate.
*/
public int getX() {
return (int) cachedPosition.x;
}

/**
* Returns the players Z coordinate.
*/
public int getZ() {
return (int) cachedPosition.z;
}

}


I hope I've provided enough code for somebody to point out the technical issue with my application. As mentioned when I tell the local player to walk, the bots with the same gender will animate with it.. ex: all male bots will animate with my male player when the male player walks

EgonOlsen

That's because they are all using the same mesh. Change this


Object3D obj = new Object3D(gender ? male : female, true);
obj.shareCompiledData(gender ? male : female);


to this


Object3D obj = new Object3D(gender ? male : female, false);
// obj.shareCompiledData(gender ? male : female);

Minigame

#2
Thank you. That worked! I figured it was only a line or two of incorrect implementations..  :-[

And instead of creating another thread I figured I would reply to the same one and ask about performance... I'm using my own distance based visibility system. Basically.. I'm processing regularly to determine if tiles / trees, etc. should be visible and have collision enabled, based on distance; here's the code:

Would it be more efficient just to call compileAndStrip and merge all of the tiles into a singe terrain since the content is 100% static?


public void update(final AbstractHuman abstractHuman) {
if ((abstractHuman.getTransformedCenter() != cachedPlayerPosition)) {
cachedPlayerPosition = abstractHuman.getTransformedCenter();
visibilityUpdateFlag = true;
}
if (visibilityUpdateFlag) {
visibilityUpdateFlag = false; // reset

if (lastVisibilityUpdate < Main.getTime() - 1200) {
lastVisibilityUpdate = Main.getTime();
/*
* Only render the tiles which are in view.
* If the tile is not in view, it does not need collision detection enabled.
*/
for (Tile tile : tileCache) {
tile.setVisibility(GameUtil.inRange(96, false, abstractHuman.getTransformedCenter(), tile.getTransformedCenter()));
tile.setCollisionMode(tile.getVisibility() ?  Object3D.COLLISION_CHECK_OTHERS : Object3D.COLLISION_CHECK_NONE);
}
/*
* Only render the trees which are in view.
*/
for (Tree tree : treeCache) {
tree.setVisibility(GameUtil.inRange(96, false, abstractHuman.getTransformedCenter(), tree.getTransformedCenter()));
}
}
}
}


Considering a single region is only 32 * 32 tiles I'm not sure if this is beneficial or causes unnecessary overhead with jPCT? In a regular lwjgl app I was developing, this helped a lot. But I'm not sure whether it's necessary here..

EgonOlsen

It might be more efficient concerning rendering performance, but it will eat up more memory and might be less efficient when it comes to collision detection. My RPG does the same thing as your approach, i.e. it does a distance check on each object. It's not a performance problem, not even on Android. I do use some additional spatial subdivision that one might call a quadtree with leaves only.