Entities
Everything you see in the game (and some things you don't see) are called an Entity. Your avatar, your avatar's gun, an enemy mob, the floor; all these things are entities. So before we can properly create the server, we need to create classes for each entity.
We'll keep things simple initially, and just create the minimum required entity types for us to be able to walk around:
- Avatars for the server
- Avatars for the player using the client
- An "other player" avatar entity for the client, to show any other players we might see in the game.
- And finally a floor, to stop us falling into infinity. We're not making Elite Dangerous here (yet).
Avatar entities are a special case, since for various reasons, they are a lot more complicated than other entities: they require player input; they require synchronizing between the client and the server (but allowing for network lag), and you don't see your own avatar. These are just some of the reasons, and that is why there are 3 types of avatar entity.
But! Before we can create any of our avatar entities, we need to create a class for the avatar models. This is required since it will tell the avatar how big it is, what 3D model to use, how to animate etc...
Create a class called WizardModel, since all our players will be wizards (it's the best model I could find on the internet), and implement the IAvatarModel interface.
You'll then have a few interface method to fill in, like so:-
import com.jme3.asset.AssetManager;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.scs.stevetech1.components.IAvatarModel;
import com.scs.stevetech1.jme.JMEModelFunctions;
public class WizardModel implements IAvatarModel {
public static final float MODEL_WIDTH = 0.4f;
private static final float MODEL_DEPTH = 0.3f;
public static final float MODEL_HEIGHT = 0.7f;
private AssetManager assetManager;
private Spatial model;
public WizardModel(AssetManager _assetManager) {
assetManager = _assetManager;
}
@Override
public Spatial createAndGetModel() {
model = assetManager.loadModel("Models/mage/mage.blend");
JMEModelFunctions.setTextureOnSpatial(assetManager, model, "Models/mage/mage.png");
JMEModelFunctions.scaleModelToHeight(model, MODEL_HEIGHT);
JMEModelFunctions.moveYOriginTo(model, 0f);
JMEAngleFunctions.rotateToWorldDirection(model, new Vector3f(-1, 0, 1)); // Point model fwds
return model;
}
@Override
public float getCameraHeight() {
return MODEL_HEIGHT - 0.2f;
}
@Override
public float getBulletStartHeight() {
return MODEL_HEIGHT - 0.3f;
}
@Override
public Vector3f getCollisionBoxSize() {
return new Vector3f(MODEL_WIDTH, MODEL_HEIGHT, MODEL_DEPTH);
}
@Override
public Spatial getModel() {
return model;
}
@Override
public void setAnim(int anim) {
// Do nothing for now
}
}
You'll notice that I've created some static numbers at the top. One of the disadvantages of using 3D models that you find on the web is that they are rarely the right size for your game, so I'm scaling the models to the size we want, and then also moving the model's origin to be the middle-bottom of the floor. With any 3D model, it's important to know where the origin is, as otherwise your avatar could be waist-deep in the ground or floating in the sky. If we set the origin to be the bottom of the model, and set the top of the floor (when we create it) to be a y-coord of 0, everything should be cool.
In the code above, the method createAndGetModel() does most of the work. In this method, we load the model and adjust it. The other methods are self-explanatory; the camera and bullet height methods are so that the camera is positioned to look out of what would be our models eyes, and (once we implement shooting) the bullets originate from somewhere appropriate on our model.
The getCollisionBoxSize() method returns our collision box. For efficiency, we use a collision box for the collisions rather than the complex 3D model itself.
Creating a Server Avatar
Create a new class called WizardServerAvatar, and extends AbstractServerAvatar, as follows:-
import com.scs.stevetech1.avatartypes.PersonAvatarControl;
import com.scs.stevetech1.entities.AbstractServerAvatar;
import com.scs.stevetech1.input.IInputDevice;
import com.scs.stevetech1.server.ClientData;
import com.scs.stevetech1.shared.IEntityController;
public class WizardServerAvatar extends AbstractServerAvatar {
public WizardServerAvatar(IEntityController _module, ClientData client, IInputDevice _input, int eid) {
super(_module, MTDClientEntityCreator.AVATAR, client, _input, eid, new WizardModel(_module.getAssetManager()), 100, 0, new PersonAvatarControl(_module, _input, 3f, 2f));
}
}
A few of the values are hard coded, but need to be the same on the client avatar code, e.g. the last two, which are the avatars movement speed and jump force. `Leet` programmers would store this in a public static var for reference in both classes.
Once you've done this, you should get an error from your IDE saying "what the heck is MTDClientEntityCreator?". This is a class we haven't created yet, so let's do it now. It's a class that, as well as actually creating entities on the client as we need them, will also contain a list of entity codes shared between the server and the client: when the server sends a message to the client telling it to create (say) a floor at co-ords (0,0,0), it specifies a floor with a code, so the client doesn't go an create a sprout or something.
So, create a new class called MTDClientEntityCreator, and add the following code to it:-
public class MTDClientEntityCreator {
public static final int AVATAR = 1;
public static final int FLOOR = 2;
}
This is all the entities we'll be creating initially, but we can add more later if need be. So now your WizardServerAvatar class should compile.
Creating a Client Avatar
Create a new class called WizardClientAvatar, and make it look like the following:-
import com.jme3.renderer.Camera;
import com.scs.stevetech1.avatartypes.PersonAvatarControl;
import com.scs.stevetech1.client.AbstractGameClient;
import com.scs.stevetech1.entities.AbstractClientAvatar;
import com.scs.stevetech1.input.IInputDevice;
public class WizardClientAvatar extends AbstractClientAvatar {
public WizardClientAvatar(AbstractGameClient _module, int _playerID, IInputDevice _input, Camera _cam, int eid, float x, float y, float z, byte side) {
super(_module, MTDClientEntityCreator.AVATAR, _playerID, _input, _cam, eid, x, y, z, side, new WizardModel(_module.getAssetManager()), new PersonAvatarControl(_module, _input, 3f, 2f));
}
}
That should compile straight away!
Creating a client-side "other player" avatar
Create a new class called OtherWizardAvatar, and make it look like the following:-import com.scs.stevetech1.entities.AbstractOtherPlayersAvatar;
import com.scs.stevetech1.shared.IEntityController;
public class OtherWizardAvatar extends AbstractOtherPlayersAvatar {
public OtherWizardAvatar(IEntityController game, int eid, float x, float y, float z, byte side, String playerName) {
super(game, MTDClientEntityCreator.AVATAR, eid, x, y, z, new WizardModel(game.getAssetManager()), side, playerName);
}
}
The last entity we need before we have something playable is a Floor, so create a Floor class and make it look like the following:-
import java.util.HashMap;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.BufferUtils;
import com.scs.simplephysics.SimpleRigidBody;
import com.scs.stevetech1.entities.PhysicalEntity;
import com.scs.stevetech1.server.Globals;
import com.scs.stevetech1.shared.IEntityController;
public class Floor extends PhysicalEntity {
public Floor(IEntityController _game, int id, float x, float yTop, float z, float w, float h, float d, String tex) {
super(_game, id, MTDClientEntityCreator.FLOOR, "Floor", false, true, false);
if (_game.isServer()) {
creationData = new HashMap<String, Object>();
creationData.put("size", new Vector3f(w, h, d));
creationData.put("tex", tex);
}
Box box1 = new Box(w/2, h/2, d/2);
box1.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(new float[]{
0, h, w, h, w, 0, 0, 0, // back
0, h, d, h, d, 0, 0, 0, // right
0, h, w, h, w, 0, 0, 0, // front
0, h, d, h, d, 0, 0, 0, // left
w, 0, w, d, 0, d, 0, 0, // top
w, 0, w, d, 0, d, 0, 0 // bottom
}));
Geometry geometry = new Geometry("FloorGeom", box1);
if (!_game.isServer()) { // Not running in server
TextureKey key3 = new TextureKey(tex);
key3.setGenerateMips(true);
Texture tex3 = game.getAssetManager().loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
Material mat = new Material(game.getAssetManager(),"Common/MatDefs/Light/Lighting.j3md"); // create a simple material
mat.setTexture("DiffuseMap", tex3);
geometry.setMaterial(mat);
}
this.mainNode.attachChild(geometry);
geometry.setLocalTranslation((w/2), -(h/2), (d/2)); // Move it into position
mainNode.setLocalTranslation(x, yTop, z); // Move it into position
this.simpleRigidBody = new SimpleRigidBody<PhysicalEntity>(this, game.getPhysicsController(), false, this);
simpleRigidBody.setNeverMoves(true);
geometry.setUserData(Globals.ENTITY, this);
} }
This class may look a bit more complicated than our "avatar" classes, but that's because most of the work for the avatar classes as done by their superclass. This class has to create it's own 3D model, position itself correctly, and (if it's been instanced by the client) apply a nice texture to itself.
Like all entities that extend PhysicalEntity, it also creates an instance of SimpleRigidBody for itself, which is used by the physics engine to determine how the entity handles in the real world. This SRB (what I'll call "SimpleRigidBody" from now on) never moves, so it's not affected by gravity, though it still blocks other entities.
Now we've created our entities, we can get back to creating the server.
Comments
Post a Comment