Hi, I want to add virtual joystick to capture movement of it, something like this maybe:
Can you give me some directions what should I read or maybe some sample code, thanks in advance
Hi, I want to add virtual joystick to capture movement of it, something like this maybe:
Can you give me some directions what should I read or maybe some sample code, thanks in advance
I think someone has used Lemur to create a virtual joystick like this. I donāt remember specifics but Lemur handles touch very well and would work fine for dragging the head of a joystick around.
ā¦wish we had a ready-made Lemur example for it.
t0neg0d has a built in virtual joystick for Android.
It really wouldnt be that tough to do this yourself.
I made a virtual joystick for android (doesnāt require any GUI lib) and use it for my lil game.
It suits my needs, but no idea how good it is. It supports multi-touch⦠actually, you could have 2 of those (or buttons) being used at the same time. Iāll post it if you donāt find better.
It only works with joystick base and stick images of same width and height. Itās a 2D joystick, not a 3D one.
It allows setting a dead zone and an out of joystick image zone that still records input. I still need to do the coding of the case where the settings ask to keep the joystick where it was when fingers not on it anymore⦠but you probably donāt need that⦠the normal mode puts the joystick at the center in that case.
NB: I kinda only tested it for my images and my case⦠I donāt think it would bug with other sizes etc, but havenāt tested. Basically, wasnāt thinking of releasing it⦠just use it, but it does work for me and the code quality is not too awful.
can you post some sample code
package com.cis.pipesandstuff.manictubes2.gizmo;
import com.cis.pipesandstuff.common.GeometryBatchFactoryUtil;
import com.cis.pipesandstuff.game.Game;
import com.jme3.input.event.TouchEvent;
import com.jme3.input.event.TouchEvent.Type;
import static com.jme3.math.FastMath.abs;
import com.jme3.math.Vector2f;
import com.jme3.ui.Picture;
import tonegod.gui.core.Screen;
/**
*
* @author jo
*/
public class JoystickGizmo extends Gizmo {
private final Vector2f positionBackground;
private Vector2f positionHandle;
private final Vector2f dimensionBackground;
private final String backgroundImg;
private final String handleImg;
private final float overhangs;//how much around the joystick background area is treated as a joystick touch too
private final float minRatio;
private boolean rememberAfterUp = false;
private float nPoint, wPoint, ePoint, sPoint;
private int joystickPointerId;
private boolean joystickDown;
private float tmpX, tmpY;
private final Vector2f tmpTouchEvent = new Vector2f();
private Vector2f tmpTranslationFromCenter = new Vector2f();
private Vector2f tmpTranslatedHandlePosition = new Vector2f();
private Vector2f fromPositionToMiddle = new Vector2f();
private Vector2f positionCenterHandle = new Vector2f();
private Picture handlePicture;
private Picture backPicture;
private float halfSize;
private Vector2f lastVectorReturned;
//currently expects handle size to be half background size
//overhangs : area around the picture who's value will still be read
//minRatio : ratio between translation and max translation under what a ZERO vector is returned
public JoystickGizmo(Game game, Screen screen, Vector2f positionBackground,
Vector2f dimensionBackground, String backgroundImg, String handleImg, float overhangs, float minRatio, boolean rememberAfterUp) {
super(game, screen);
this.game = game;
this.screen = screen;
this.positionBackground = positionBackground;
this.dimensionBackground = dimensionBackground;
this.backgroundImg = backgroundImg;
this.handleImg = handleImg;
this.overhangs = overhangs;
this.minRatio = minRatio;
this.rememberAfterUp = rememberAfterUp;
}
@Override
public void initialize() {
//calculate the cardinal extremities
nPoint = positionBackground.y + dimensionBackground.x + overhangs;
sPoint = positionBackground.y - overhangs;
wPoint = positionBackground.x - overhangs;
ePoint = positionBackground.x + dimensionBackground.x + overhangs;
fromPositionToMiddle = dimensionBackground.mult(0.25f);
positionHandle = positionBackground.add(fromPositionToMiddle);
positionCenterHandle = positionHandle.add(fromPositionToMiddle);
halfSize = dimensionBackground.x / 2f;
joystickPointerId = -1;
joystickDown = false;
lastVectorReturned = Vector2f.ZERO;
}
private void display() {
backPicture = new Picture("backPic");
backPicture.setImage(game.getAssetManager(), backgroundImg, true);
backPicture.setWidth(dimensionBackground.getX());
backPicture.setHeight(dimensionBackground.getY());
backPicture.setPosition(positionBackground.x, positionBackground.y);
backPicture.setUserData(GeometryBatchFactoryUtil.FLAG_FOR_OPTIMIZE, "stuff");
game.getGuiNode().attachChild(backPicture);
handlePicture = new Picture("handlePic");
handlePicture.setImage(game.getAssetManager(), handleImg, true);
handlePicture.setWidth(dimensionBackground.getX() / 2f);
handlePicture.setHeight(dimensionBackground.getY() / 2f);
handlePicture.setPosition(positionHandle.x, positionHandle.y);
game.getGuiNode().attachChild(handlePicture);
}
//returns false when not a joystick event
//returns Vector2f.ZERO when jotsick in rest position (middle)
//sets the lastVectorValue when should otherwise keeps previous value
public boolean lookIntoIt(TouchEvent touchEvent) {
if (!joystickDown) {
if (touchEvent.getType() == Type.DOWN) {
if (touchEvent.getY() < nPoint && touchEvent.getY() > sPoint && touchEvent.getX() > wPoint && touchEvent.getX() < ePoint) {
tmpTouchEvent.set(touchEvent.getX(), touchEvent.getY());
tmpTranslationFromCenter = tmpTouchEvent.subtract(positionCenterHandle);
tmpTranslatedHandlePosition = positionHandle.add(tmpTranslationFromCenter);
handlePicture.setPosition(tmpTranslatedHandlePosition.x, tmpTranslatedHandlePosition.y);
joystickPointerId = touchEvent.getPointerId();
joystickDown = true;
tmpTranslationFromCenter.x = tmpTranslationFromCenter.x / halfSize;
tmpTranslationFromCenter.y = tmpTranslationFromCenter.y / halfSize;
return applyMinRatioAndSetValues(tmpTranslationFromCenter);
} else {
return false;
}
} else {
return false;
}
}
if (touchEvent.getPointerId() != joystickPointerId) {
return false;
}
switch (touchEvent.getType()) {
case UP:
if (rememberAfterUp) {
//same as MOVE - todo
lastVectorReturned = Vector2f.ZERO;
return true;
} else {
handlePicture.setPosition(positionHandle.x, positionHandle.y);
joystickDown = false;
joystickPointerId = - 1;
lastVectorReturned = Vector2f.ZERO;
return true;
}
case MOVE:
tmpX = (touchEvent.getX() < wPoint) ? wPoint : touchEvent.getX();
tmpX = (tmpX > ePoint) ? ePoint : tmpX;
tmpY = (touchEvent.getY() > nPoint) ? nPoint : touchEvent.getY();
tmpY = (tmpY < sPoint) ? sPoint : tmpY;
tmpTouchEvent.set(tmpX, tmpY);
tmpTranslationFromCenter = tmpTouchEvent.subtract(positionCenterHandle);
tmpTranslatedHandlePosition = positionHandle.add(tmpTranslationFromCenter);
handlePicture.setPosition(tmpTranslatedHandlePosition.x, tmpTranslatedHandlePosition.y);
tmpTranslationFromCenter.x = tmpTranslationFromCenter.x / halfSize;
tmpTranslationFromCenter.y = tmpTranslationFromCenter.y / halfSize;
return applyMinRatioAndSetValues(tmpTranslationFromCenter);
}
return true;
}
private boolean applyMinRatioAndSetValues(Vector2f value) {
lastVectorReturned = value;
if (minRatio != 0) {
if( abs(lastVectorReturned.x ) < minRatio) { lastVectorReturned.x = 0; }
if( abs(lastVectorReturned.y ) < minRatio) { lastVectorReturned.y = 0; }
if( value.x == 0f && value.y == 0f ){ lastVectorReturned = Vector2f.ZERO; }
}
return true;
}
public Vector2f getLastVectorValue(){
return lastVectorReturned;
}
public void enable() {
display();
}
public void disable() {
}
@Override
public void clean() {
game.getGuiNode().detachChild(backPicture);
game.getGuiNode().detachChild(handlePicture);
}
@Override
public void update(float tpf) {
}
}
package com.cis.pipesandstuff.manictubes2.appstate;
import com.cis.pipesandstuff.game.Game;
import com.cis.pipesandstuff.manictubes2.gizmo.JoystickGizmo;
import com.jme3.app.Application;
import com.jme3.math.Vector2f;
import tonegod.gui.core.Screen;
/**
*
* @author jo
*/
public class JoystickAppState extends BaseAppState{
private Game game;
private Screen screen;
private JoystickGizmo joystick;
private static final float WIDTH = 150;
private static final float HEIGHT = 150;
public JoystickAppState(){
}
@Override
protected void initialize(Application app) {
this.game = (Game)app;
this.screen = game.getGameManager().getScreen();
joystick = new JoystickGizmo(game,
screen,
new Vector2f(60f, 60f),
new Vector2f(WIDTH, HEIGHT),
"Textures/Gizmos/Joystick/joystickBackground.png",
"Textures/Gizmos/Joystick/joystickHandle.png",
5, 0.50f, false);
joystick.initialize();
// enable();
}
@Override
public void update( float tpf ) {
// joystick.update();
}
@Override
protected void cleanup(Application app) {
joystick.clean();
}
@Override
protected void enable() {
joystick.enable();
}
@Override
protected void disable() {
joystick.disable();
}
public JoystickGizmo getJoystick() {
return joystick;
}
}
Gonna add some explanations here⦠just wanted to get the main classes out of the way first.
Couple remarks first:
How it works: when a touch event occurs, the joystick class calculates the current displacement of the joystick but doesnāt call anything.
The onTick method of the listener checks the previously calculated displacement and reacts to it. Why? Because android does not send events continuously when your finger doesnāt move⦠but I wanted my vehicle to still get the acceleration etc x times per second.
Your listener should looks something like this:
public class xxxAndroidControlsListener implements xxxControlsListener, TouchListener, TickListener {
private final Game game;
private final JoystickGizmo joystickGizmo;
private Vector2f lookedIntoItJoystickValue;
public InputCarAndroidControlsListener(Game application, WorldPlayer worldPlayer) {
this.game = application;
joystickGizmo = application.getStateManager().getState(JoystickAppState.class).getJoystick();
lookedIntoItJoystickValue = Vector2f.ZERO;
isJoysTickEvent = false;
}
@Override
public void onTick(float tpf){
if(lookedIntoItJoystickValue == Vector2f.ZERO){
//reset acceleration, yaw, etc
} else {
if(lookedIntoItJoystickValue.x == 0f){
//reset yaw
} else if(lookedIntoItJoystickValue.x > 0f){
//steer left
} else {
//steer right
}
if(lookedIntoItJoystickValue.y == 0f){
//reset acceleration
} else if(lookedIntoItJoystickValue.y > 0f){
//accelerate
} else {
//brake
}
}
}
private boolean tamponEventLookedIntoIt;
private boolean isJoysTickEvent;
@Override
public void onTouch(String name, TouchEvent event, float tpf) {
isJoysTickEvent = joystickGizmo.lookIntoIt(event);
lookedIntoItJoystickValue = joystickGizmo.getLastVectorValue();
if(!joysTickEvent){
//buttons or other joystick reaction to would come here
}
}
@Override
public void setIsEnabledInputs(boolean enabled) {
final InputManager inputManager = game.getInputManager();
if (enabled) {
((ManicTubes2)game).setTickListener(this);
inputManager.addMapping("Main_Touch_All", new TouchTrigger(TouchInput.ALL));
inputManager.addListener(this, "Main_Touch_All");
} else {
((ManicTubes2)game).setTickListener(null);
inputManager.deleteMapping("Main_Touch_All");
}
}
}
ā((ManicTubes2)game).setTickListener(this);ā this is to get a tick, but that tick probably should come from the joystickAppState instead.
Anyway, thatās the gist of it.
Well, thatās one guy Iāll never move a finger for again.
Well thank you for posts but I do not quite understand the part with the listener, how to use it or what should I read about, which are the packages of the interfaces?
Listenerās imports (ones not specific to my game):
import com.jme3.input.InputManager;
import com.jme3.input.TouchInput;
import com.jme3.input.controls.TouchListener;
import com.jme3.input.controls.TouchTrigger;
import com.jme3.input.event.TouchEvent;
import com.jme3.math.Vector2f;
TickListener interface:
/**
*
* @author jo
*/
public interface TickListener {
public void onTick(float tpf);
}
How to use the listener:
You have to feed the tick to your listener and code how it reacts to it.
I currently have ticks fed to this listener by calling the listenerās setIsEnabledInputs() which registers the listener with the game class and get itās tick events.
Game class tick stuff:
@Override
public void simpleUpdate(float tpf) {
if(tickListener != null){
tickListener.onTick(tpf);
}
}
//set to null when done
public void setTickListener(TickListener tickListener){
this.tickListener = tickListener;
}
I wonder why my appState isnāt feeding the ticks instead of the Game⦠either bad design or it avoids a specific problem⦠I donāt remember :.
As far as I know, this is the only real virtual joystick for android jme. I havenāt looked at any implementations and so it is totally open source.
All I required for people to use/base a joystick on it, is a thanks which is why I went a lil berzerk ;).
No promises (Iām a slacker), but Iāll look at releasing it as a generic library on bitbucket with a small usage example in the coming days.
Ok, since it was probably cryptic like hell, I made:
what jme version you are using com.jme3.app.state.BaseAppState in not on my classpath i m using jme 3.1, i will try to replace it with AbstractAppState see if will start
Which 3.1? Ancient alpha 1? Or?
nah my bad the ide is:
Product Version: jMonkeyEngine SDK 3.0
Updates: Updates available
Java: 1.7.0_51; Java HotSpot⢠Server VM 24.51-b03
Runtime: Java⢠SE Runtime Environment 1.7.0_51-b13
System: Linux version 3.16.0-49-generic running on i386; UTF-8; en_US (jmonkeyplatform)
I created the project with the wizard from jmonkey studio.
Maybe i m missing some jar from my lib? I will try add BaseAppState from sources in github. The class is supposed to be in jME3-core.jar in /com/jme3/app/state/ but its not thereā¦
ā¦yes, because you are running an ancient version of JME. My son was still in elementary school when that version came out.
I managed to run it thank you loopies
Goodie. Tell me if you find bugs or such.