Long time since I was the last time in this forum. Nevertheless, I worked a lot on my game.
Here a short clip about a free fall animation. Superhero landing coming up Actually that is the next refinement to add a 3 point superhero landing. But I fear it will not be really visible, but anyway I will give it a try.
2 weeks of hardworking on something with no Wiki , even java docs , the New animation system (probably covered everything ) including :
- AnimComposer
- AnimClips
- TransformTracks
- Armature & Joints Nodes
- BaseAction & Tweens
- BlendableActions , ClipActions & Actions
- Emitter , Audio & Model (Concretes of HasLocalTransform) in parallel , sequence or sineStep Tweens
- Multiple AnimComposers on the same Model.
- Pause , resume , play animations as AppStates
Here’s a video of the progress , probably have finished their basic documentation , I will finalize the work & send here for review , before doing a PR or looking at the wiki :
Notice that this app uses JmeSurfaceView on a androidx.appCompat.Fragment.
This week I’m working on a rigging tool that will let me (among other things) define collision shape hierarchies for animated models in a way that is compatible with the MOSS custom physics engine.
I may end up with multiple layers to more easily balance between regular walking around physics (like in this picture) versus “which body part did the sword cut off” physics which would require a more complete hierarchy but otherwise wouldn’t need to be updated from frame to frame.
Still 100 open questions left to solve but progress is progress.
Is that a fixed mesh/model, or an isosurface/smooth voxel?
A good old mesh. I only use tris and quad. Moving more towards quads recently.
In the last few days I worked on an Arkanoid clone just for fun. It’s freely available for download on itch.io. Have fun!
in this case you need search “Topology” keyword.
Topology is very important.
But if you want skip making face, just download MakeHuman and export it and edit if you wish.
Oh, yes I know. I’m using correct topology. If you pay attention my quads are not placed randomly, they actually follow a pattern.
The repo of current work :
Working on a FPS & maze game (with way more success than my other wips)!
- Implemented a custom movement framework using raycasts (thanks @yaRnMcDonuts!)
- Random grid maze generation
- Shootable enemies
- A solid environment that doesn’t fly apart as soon as you shoot it.
- Runs at 60 fps!!!
To do:
- Fix sticky collisions
- Implement a minimum range of the gun
- Better models (gun and enemies)
- Barrel flash
- Fix gun orientation
- Bullet holes in walls
- Set gun to semi-auto
- Add reloading?
Yup, a lot more to do!
Continue working on my tower defense game.
What was implemented after the latest report?
- New weapon model
- Preview of a weapon on creation
- Weapons now has an attack range
- Algorithm for next attack search
- Weapons rotating to the direction of attack
- Total score for the game and adding points to it on enemy death
- HP for cave and last barrier (in this case door) as a target to protect
- Some other fixes
Of course, a lot of things need to be done and a lot of ideas in my head, so continue working on it
Comments and advice are appreciated
why i feel like its Gothic 3 music
It is from Lineage 2, Dion theme if I’m not mistaken
ah yes, might be, i just did remember it was some game
Hello everybody,
I’m updating some old experiments with the cinematic package and the old animation system.
I hope to try all the upgrades to the new animation system when the next jme-3.4.0-alpha8 release comes out.
I wrote my custom class on the example of that AnimationEvent to prevent the animation from being reset at the end of the cutscene.
package jme.test.cinematic;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application;
import com.jme3.cinematic.Cinematic;
import com.jme3.cinematic.events.AbstractCinematicEvent;
import static com.jme3.cinematic.events.AnimationEvent.MODEL_CHANNELS;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.scene.Spatial;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author capdevon
*/
public class MyAnimEvent extends AbstractCinematicEvent {
private static final Logger logger = Logger.getLogger(MyAnimEvent.class.getName());
private Cinematic cinematic;
private Spatial model;
private String animName;
private AnimChannel channel;
private int channelIndex = 0;
private float blendTime = 0.15f;
public MyAnimEvent(Spatial model, String animName) {
super(LoopMode.DontLoop);
this.model = model;
this.animName = animName;
this.initialDuration = model.getControl(AnimControl.class).getAnimationLength(animName);
}
/**
* creates an animation event
*
* @param model the model on which the animation will be played
* @param animName the name of the animation to play
* @param loopMode the loopMode
* @see LoopMode
* @param channelIndex the index of the channel default is 0.
*/
public MyAnimEvent(Spatial model, String animName, LoopMode loopMode, int channelIndex) {
super(loopMode);
this.model = model;
this.animName = animName;
this.channelIndex = channelIndex;
this.initialDuration = model.getControl(AnimControl.class).getAnimationLength(animName);
}
@Override
@SuppressWarnings("unchecked")
public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic);
this.cinematic = cinematic;
if (channel == null) {
Object s = cinematic.getEventData(MODEL_CHANNELS, model);
if (s == null) {
s = new HashMap<Integer, AnimChannel>();
int numChannels = model.getControl(AnimControl.class).getNumChannels();
for (int i = 0; i < numChannels; i++) {
((Map<Integer, AnimChannel>) s).put(i, model.getControl(AnimControl.class).getChannel(i));
}
cinematic.putEventData(MODEL_CHANNELS, model, s);
}
Map<Integer, AnimChannel> map = (Map<Integer, AnimChannel>) s;
channel = map.get(channelIndex);
if (channel == null) {
channel = model.getControl(AnimControl.class).createChannel();
map.put(channelIndex, channel);
}
logger.log(Level.INFO, "AnimEvent initialized: {0}, AnimName: {1}", new Object[]{model, animName});
}
}
@Override
@SuppressWarnings("unchecked")
public void dispose() {
super.dispose();
if (cinematic != null) {
Object objMap = cinematic.getEventData(MODEL_CHANNELS, model);
if (objMap != null) {
Collection<AnimChannel> values = ((HashMap<Integer, AnimChannel>) objMap).values();
while (values.remove(channel));
if (values.isEmpty()) {
cinematic.removeEventData(MODEL_CHANNELS, model);
}
}
cinematic = null;
channel = null;
}
}
@Override
public void onPlay() {
channel.getControl().setEnabled(true);
channel.setTime(0);
}
@Override
public void onUpdate(float tpf) {
if (!animName.equals(channel.getAnimationName())) {
channel.setAnim(animName, blendTime);
channel.setSpeed(speed);
channel.setLoopMode(loopMode);
}
}
@Override
protected void onStop() {
}
@Override
public void onPause() {
if (channel != null) {
channel.getControl().setEnabled(false);
}
}
@Override
public void setSpeed(float speed) {
super.setSpeed(speed);
if (channel != null) {
channel.setSpeed(speed);
}
}
@Override
public void setLoopMode(LoopMode loopMode) {
super.setLoopMode(loopMode);
if (channel != null) {
channel.setLoopMode(loopMode);
}
}
@Override
public void write(JmeExporter ex) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void read(JmeImporter im) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
}
For the sliding door I used a RigidBodyControl in kinematic mode and 2 animations created via AnimationFactory.
Spatial door = ...;
addBoxCollider(door, 100f, true);
SlidingDoorControl control = new SlidingDoorControl();
door.addControl(control);
public RigidBodyControl addBoxCollider(Spatial sp, float mass, boolean kinematic) {
BoundingBox bb = (BoundingBox) sp.getWorldBound();
CollisionShape shape = new BoxCollisionShape(bb.getExtent(null));
RigidBodyControl rgb = new RigidBodyControl(shape, mass);
sp.addControl(rgb);
if (mass > 0) {
rgb.setKinematic(kinematic);
}
physics.getPhysicsSpace().add(rgb);
return rgb;
}
// Edit: snippets updated with the new animation system jme-3.4.0-beta1
public class SlidingDoorControl extends AbstractControl implements AnimEventListener {
private AnimComposer animComposer;
private AnimChannel animChannel; //deprecated
private final String openDoor = "Open";
private final String closeDoor = "Close";
public float animSpeed = 1f;
...
@Override
public void setSpatial(Spatial sp) {
super.setSpatial(sp);
if (spatial != null) {
...
// addAnimControl(); //deprecated
addAnimComposer();
}
}
@Override
protected void controlUpdate(float tpf) {
...
}
private void addAnimComposer() {
animComposer = new AnimComposer();
buildAnimation(openDoor, State.OPEN, origin, offset);
buildAnimation(closeDoor, State.CLOSE, offset, origin);
spatial.addControl(animComposer);
}
private void buildAnimation(String animName, State newState, Vector3f v1, Vector3f v2) {
float duration = 1f;
AnimFactory factory = new AnimFactory(duration, animName, 25f);
factory.addTimeTranslation(0, v1);
factory.addTimeTranslation(duration, v2);
AnimClip animClip = factory.buildAnimation(spatial);
animComposer.addAnimClip(animClip);
// Get action registered with specified name. It will make a new action if there isn't any.
Tween delegate = animComposer.action(animName);
// Configure custom action
Tween dontLoop = Tweens.callMethod(animComposer, "removeCurrentAction", AnimComposer.DEFAULT_LAYER);
Tween changeState = Tweens.callMethod(this, "changeState", newState);
BaseAction myAction = new BaseAction(Tweens.sequence(delegate, changeState, dontLoop));
// Register custom action with specified name.
animComposer.addAction(animName, myAction);
}
void changeState(State newState) {
this.state = newState;
System.out.println("onStateChanged: " + state);
}
@Deprecated
private void addAnimControl() {
AnimControl animControl = new AnimControl();
animControl.addAnim(buildAnimation(openDoor, 1, origin, offset));
animControl.addAnim(buildAnimation(closeDoor, 1, offset, origin));
spatial.addControl(animControl);
animChannel = animControl.createChannel();
animControl.addListener(this);
}
@Deprecated
private Animation buildAnimation(String name, float duration, Vector3f v1, Vector3f v2) {
AnimationFactory factory = new AnimationFactory(duration, name);
factory.addTimeTranslation(0, v1);
factory.addTimeTranslation(duration, v2);
return factory.buildAnimation();
}
public void setAnimation(String animName) {
if (animComposer != null && animComposer.getAction(animName) != animComposer.getCurrentAction()) {
animComposer.setCurrentAction(animName);
animComposer.setGlobalSpeed(animSpeed);
} else if (animChannel != null && !animName.equals(animChannel.getAnimationName())) {
// deprecated
animChannel.setAnim(animName);
animChannel.setSpeed(animSpeed);
animChannel.setLoopMode(LoopMode.DontLoop);
}
}
@Override
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
//deprecated
...
}
@Override
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
//deprecated
...
}
}
I used classic triggers to identify the player’s position in front of the door panel or behind the enemy.
In the example of stealth kills, I tried to create some movement with the camera, whose dynamic position is calculated based on the orientation of the player and the enemy.
Some reference snippets of the code:
// Edit: snippets updated with the new animation system jme-3.4.0-beta1
public class CinematicKillAction extends AbstractCinematicScript implements ColliderListener {
String actionName = "Action.Button";
boolean isInteracting;
BitmapText interactionUI;
private Spatial spTarget;
private Cinematic cinematic;
//private MyAnimEvent deathAnimEvent; //old anim system
private AnimEvent deathAnimEvent; //new anim system
private float shotTime = 0;
...
@Override
public void onAction(String action, boolean isPressed, float tpf) {
if (isInteracting) {
return;
}
if (action.equals(actionName) && isPressed) {
if (spTarget != null) {
playCinematic();
}
}
}
@Override
public void onCollisionEnter(Collider trigger) {
interactionUI.setText(("onCollisionEnter: " + trigger.getTagName());
this.spTarget = trigger.getSpatial();
}
@Override
public void onCollisionExit(Collider trigger) {
interactionUI.setText("onCollisionExit: " + trigger.getTagName());
this.spTarget = null;
}
private void playCinematic() {
Vector3f dirToTarget = FVector.subtract(spatial, spTarget).normalizeLocal();
Vector3f camPosition = spTarget.getWorldTranslation().add(0, 3, 0).add(dirToTarget.mult(4));
midPoint.interpolateLocal(spatial.getWorldTranslation(), spTarget.getWorldTranslation(), 0.5f);
ccPlayer.setViewDirection(dirToTarget);
ccPlayer.setWalkDirection(Vector3f.ZERO);
CameraNode cineCam = cinematic.getCamera("Cinematic.Camera");
cineCam.setLocalTranslation(camPosition);
cineCam.lookAt(midPoint, Vector3f.UNIT_Y);
if (deathAnimEvent != null) {
cinematic.removeCinematicEvent(deathAnimEvent);
}
/* old animation system
CinematicCharacterControl ccEnemy = spTarget.getControl(CinematicCharacterControl.class);
deathAnimEvent = new MyAnimEvent(ccEnemy.getRootMotion(), "DeathBack");
cinematic.addCinematicEvent(shotTime + 0.2f, deathAnimEvent);
*/
// new animation system
AnimComposer ccEnemy = AnimUtils.getAnimControl(spTarget);
deathAnimEvent = new AnimEvent(ccEnemy, "DeathBack", AnimComposer.DEFAULT_LAYER);
cinematic.addCinematicEvent(shotTime + 0.2f, deathAnimEvent);
cinematic.fitDuration();
cinematic.setSpeed(1.2f);
cinematic.play();
}
private void initCinematicEvents() {
float duration = 6f;
cinematic = new Cinematic(getRootNode(), duration);
app.getStateManager().attach(cinematic);
// bind a camera to the cinematic with a unique name.
CameraNode cineCam = cinematic.bindCamera("Cinematic.Camera", app.getCamera());
cineCam.setLocalTranslation(Vector3f.UNIT_Y);
cinematic.activateCamera(0, cineCam.getName());
cinematic.addCinematicEvent(0, new SoundEvent("Sound/Environment/Nature.ogg", LoopMode.Loop));
cinematic.addCinematicEvent(0, new SoundEvent("Sound/Effects/kick.wav"));
cinematic.enqueueCinematicEvent(new MyAnimEvent(ccPlayer.getRootMotion(), "PistolAim_2"));
shotTime = cinematic.enqueueCinematicEvent(new SoundEvent("Sounds/Weapons/gun-shot.ogg"));
cinematic.addListener(this);
}
@Override
public void onPlay(CinematicEvent evt) {
isInteracting = true;
interactionUI.setText("Playing cutscene...");
spTarget.getControl(CinematicCharacterControl.class).setCinematicMode(true);
ccPlayer.setCinematicMode(true);
}
@Override
public void onStop(CinematicEvent evt) {
spTarget.getControl(CinematicCharacterControl.class).setCinematicMode(false);
ccPlayer.setCinematicMode(false);
spTarget = null;
isInteracting = false;
interactionUI.setText("");
}
@Override
public void onPause(CinematicEvent cinematic) {
}
}