(April 2021) Monthly WIP Screenshot Thread

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 :smiley: 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.

9 Likes

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.

12 Likes

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.

11 Likes

New Garden area level.


*tree transparency is turned off.

5 Likes

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!

15 Likes


Character modeling practice.

3 Likes

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.


End of this week’s practice.

3 Likes

The repo of current work :

4 Likes

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!

9 Likes

Modeled some low poly corpses and frozen people for environmental story telling.

Imgur
Imgur
Imgur

14 Likes

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 :slight_smile:
Comments and advice are appreciated

10 Likes

why i feel like its Gothic 3 music :smiley:

It is from Lineage 2, Dion theme if I’m not mistaken :slight_smile:

1 Like

ah yes, might be, i just did remember it was some game :slight_smile:

1 Like

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) {
	}
}
14 Likes

@capdevon damn, nice!

1 Like