Sign in to follow this  
thelegendofzaku

8 way movement in MIDP 2.0

Recommended Posts

I'm trying to expand on this snippet of code that I got off of a tutorial. What I'm trying to do is to expand it so that I can move diagonally. I already know that the diagonals have to be mapped on the number pad, I'm just not sure as to how to move a sprite on both axes simultaneously. Here's what I want to expand on: static final int MOVE = 4//How many pixels to move in every iteration. public void moveLeft() { getXY();//This method calls up the x and y coordinates of the GameCanvas a.k.a. the game screen. if (x - MOVE > 0)//To move left, you would have to make sure that the difference of the x value and the move variable are greater than zero so that the sprite is contained in the screen. move(MOVE * -1,0);//The move method takes in two parameters: x and y, but in a certain way, like to move a sprite over the X plane, a positive x would move it to the right while an negative x moves it to the left. The same goes for the Y plane in that a negative y moves it up while a positive moves it down. } public void moveRight() { getXY(); if (x + MOVE + frameWidth < scnWidth)//This condition contains the sprite movements within the width of the screen in order to move right, so that the sprite does not move out of sight. move(MOVE,0); } public void moveUp() { getXY(); if (y - MOVE > 0)//This is just like moveLeft() in that the y value is negative. move(0,MOVE * -1); } public void moveDown() { getXY(); if (y + MOVE + frameHeight < scnHeight) move(0,MOVE);//JUst like moveRight(), it moves the sprite down, but only up to the very bottom of the screen. }

Share this post


Link to post
Share on other sites
spillz    122
quick and dirty would be to call (for up and left)

public void moveUpLeft(){
moveLeft();
moveUp();
}

problem is the sprite will move much quicker going along diagonals. the distance of the diagonal move can be worked out with pythagoras theorem:

MOVE^2=XMOVE^2 + YMOVE^2

obviously MOVE is always more than XMOVE (horizontal) or YMOVE (vertical)

so assuming you want a diagonal move to be the same distance as move and assuming you want your diagonal move to be comprised of a fixed horizontal and vertical move (DMOVE) then you set

DMOVE = MOVE/(2^0.5)

where 2^0.5 ~ 1.41 = 141/100 (~ means approximately equal to)

so now you could write your diagonal move code to be something along the lines of:

static final int DMOVE = MOVE*100/141 // if MOVE =4 then DMOVE will round to 3 so your diag move will take you 3 pixels horizontally and 3 pixels vertically

public void moveUpLeft(){
getXY();
if(x-DMOVE>0&&y-DMOVE>0) // check that the sprite stays on screen
move(-DMOVE,-DMOVE);
}

the other directions will be similar.

some other things to think about:

1. do you want the sprite to be able to go all the way to the edge of the screen? your current code won't allow this unless the screen dimensions happen to be multiples of 4.

2. you are using lots of method calls for very simple code. for instance you call moveLeft which then calls getXY and move. while academics love embedding even the smallest new functionality in a new method, on devices with limited performance (like mobiles) this can slow down your program. all of this movement could be combined into a sequence of if statements without any method calls, just directly manipulating the x and y variables. its probably not a big deal for a simple program but as your program gets more complex all these redundant method calls will mean you have less time to paint sprites on the screen.

Share this post


Link to post
Share on other sites
Quote:
Original post by spillz
1. do you want the sprite to be able to go all the way to the edge of the screen? your current code won't allow this unless the screen dimensions happen to be multiples of 4.

2. you are using lots of method calls for very simple code. for instance you call moveLeft which then calls getXY and move. while academics love embedding even the smallest new functionality in a new method, on devices with limited performance (like mobiles) this can slow down your program. all of this movement could be combined into a sequence of if statements without any method calls, just directly manipulating the x and y variables. its probably not a big deal for a simple program but as your program gets more complex all these redundant method calls will mean you have less time to paint sprites on the screen.


1) Yeah, as long as I'm able to contain the sprite within the dimensions of the screen while the BG scrolls. As i said, since the tutorial I got the code from is tailored for a vertical side scroll shooter, I plan on making a horizonal side scroller. FYI, the dimensions of the phone I'm making it for (Siemens M56) are 101x80, so in theory I could change it to move in fours.

2) I guess the author of the tutorial managed to modularize the sprite movements, hence the snippet is actually part of a class that draws out a player sprite and he calls its methods in a game screen class encapsulated in a tick method.

Anyways, I'll give the code a try tomorrow, since I gotta get some sleep now, got a early morning class to get to.

Share this post


Link to post
Share on other sites
OK, since that new method won't work until I get a scrolling tiled layer, I was wondering if there was another way to do this. Consider this following method:

private void input() {
int keyStates = getKeyStates();

sprite.setFrame(0);

// Left
if ((keyStates & LEFT_PRESSED) != 0) {
currentX = Math.max(0, currentX - 1);
sprite.setFrame(1);
}

// Right
if ((keyStates & RIGHT_PRESSED) !=0 )
if ( currentX + 5 < width) {
currentX = Math.min(width, currentX + 1);
sprite.setFrame(3);
}

// Up
if ((keyStates & UP_PRESSED) != 0) {
currentY = Math.max(0, currentY - 1);
sprite.setFrame(2);
}

// Down
if ((keyStates & DOWN_PRESSED) !=0)
if ( currentY + 10 < height) {
currentY = Math.min(height, currentY + 1);
sprite.setFrame(4);
}
}
.
.
.
.
private void drawScreen(Graphics g) {
//typical drawing goes here.
sprite.setPosition(currentX,currentY);
//same here too.
flushGraphics();
}

What the input method does is that it constrains movement to each side. In other words, for example, the left algorithm sets the current X position from the 0, or the left hand side of the background, to the current X position being subtracted by 1. Bottom line, each direction is referenced from a max point in each side, and so the X and Y values are either added or subtracted towards or away from that point. How would I apply the same pythagorean concepts to this method so that it pipes diagonal points to the X and Y variables so that they can be repainted?

Share this post


Link to post
Share on other sites
spillz    122
your latest version is a big improvement...

a few notes:

firstly, by default that code will handle diagonal movement for any device that handles multiple key presses since you check each key independently (e.g. up and left simulteneously will move up and left by 1 space in each direction).

as before the diagonal movement distance will be greater than a vertical/horizontal movement, but this isn't necessarily a problem (depends on what you want to acheive in your game). if you wanted to implement a fixed distance move, you will need to increase the basic movement step to something like 3 units for a horizontal or vertical movement, and use say 2 units horizontal and vertical for a diagonal movement since you can't move fractions of a pixel (ALTERNATIVELY you could scale up your X,Y coordinates by 10 or 100 giving you a finer grid of movement to work with - just integer divide the coordinates by 10 or 100 before you draw the sprites to the screen).

example code for simple fixed distance key processing:

//first handle possible combination of diagonal key press pairs
if((keyState&UP)!=0&&(keyState&LEFT)!=0){
currentX = Math.max(0, currentX - 2);
currentY = Math.max(0, currentY - 2);
}
else
if(/*other diagonal keyStates*/){
...
}
...
else //now handle the vertical/horizontals
if(((keyState&UP)!=0){
currentY = Math.max(0, currentY - 3);
}
else
...

if you want an actual key for each of the diagonal moves you cannot really use the getKeyStates methods, because it only has "game" keys that don't necessarily map to specific number buttons (they're device dependant). to map direction to actual number keys override the onKeyPressed and onKeyReleased methods to set your own keyState variable(s). search threads here or on www.j2me.org for plenty of tutorials on this (i can give you sample code if necessary)

you should also think about making your code reasonably screen size independent. there is plenty of variation in screen resolution and aspect ratios. you don't necessarily need to use the maximum resolution available, but its nice to center your game on screen and position status indicators sensibly.

Share this post


Link to post
Share on other sites
Well, I can safely say that good 'ol Pythagorean Theroem paid off when it came to moving the sprite diagonally, as well as optimizing the process since I encapsulated it in a method in a seperate Sprite class called up in the game screen class, which uses GameCanvas, so that's already taken care of. However, I face a new problem. You're right, I have to map out the diagonals to key codes since key states only look out for key presses in the upper half of the phone (d-pad, soft buttons, etc...). Problem is, how do I invoke them in the main game loop since a key state can be encapsulated in a method and piped through the loop? What makes this even worst is the so called bug that my phone maker's game API (Siemens Game API for MIDP 1.0, which is their own implementation of the MIDP 2.0 Game API) has, in which if I override the keyPressed, keyReleased, or keyRepeated methods, it will always set my key states to zero, thus I can't move the sprite at all. Also, it seems that my phone's emulator automatically maps key states to 2, 4, 6, and 8, so my only concern is how do I map the diagonal methods into 1, 3, 7, and 9, as well as invoking them in the main game loop: Enclosed is the example code that I've been working on as well as the link to a thread in the Siemens (now Benq Mobile) developer's forum about the bug's discovery:

http://agathonisi.erlm.siemens.de:8080/jive3/thread.jspa?forumID=6&threadID=15784&messageID=57992#57992

the code:

EDIT: new code is a few posts down.

EDIT2: Also enclosed is a thread over in J2ME.org in which another user reports of the same flaw.

http://www.j2me.org/yabbse/index.php?board=12;action=display;threadid=5068

[Edited by - thelegendofzaku on December 1, 2005 10:08:29 PM]

Share this post


Link to post
Share on other sites
spillz    122
try calling super(keyCode) in your onKeyPressed and onKeyReleased method overrides. otherwise implement all key strokes (including game keys) yourself. be careful because the keyCodes have different codes than getKeyStates

Share this post


Link to post
Share on other sites
Quote:
Original post by spillz
try calling super(keyCode) in your onKeyPressed and onKeyReleased method overrides. otherwise implement all key strokes (including game keys) yourself. be careful because the keyCodes have different codes than getKeyStates


I tried mapping key strokes but I can't seem to invoke them in the main game loop. What's the best way of mapping the keys assuming I encapsulated them in a keyPressed method, since I tried looking on the web for those tutorials and could not find one that dealt with using those methods above and passing it through a main game loop, almost all of them deal with key states and others deal with the above methods in a 1.0 Canvas, not the Game API's GameCanvas class. Feel free to change part of the code to reflect such.

Share this post


Link to post
Share on other sites
spillz    122
keyPressed and keyReleased are invoked from the midlets main thread not your game thread (most of the applications messages are passed to the midlets main thread by default). you shouldn't do any extensive processing on the main thread, just record the key events. one way to do this is to declare boolean variables in the gamecanvas for the state of each of the keys: true if pressed, false if released. in your method overrides just update the status of the keys. save the actual movement for the input method based on what key is being pressed.

also, do try calling super(keyCode) from your onKeyPressed/onKeyReleased override as this may resolve the getKeyStates issue (calling super calls the Siemens API implementations of these if there are any). i would be interested to see whether the Siemens API is implementing the gamecanvas' getKeyStates methods by overriding onKeyPressed/onKeyReleased.

Share this post


Link to post
Share on other sites
I managed to move the sprite w/o using key states. All I did was use the keyPressed method in the game canvas and piped the move method in the main game loop using a threaded loop. However, I'm having trouble mapping it out to 1, 3, 7, and 9 using this method. The way I was gonna approach this is I was gonna use the getGameActions method for the d-pad and the getKeyCode method for the rest of the keys. Problem is, that oviously, the getGameActions method only gets the codes for the upper d-pad and soft keys. How would I use the getKeyCode method to effectively map the key numbers? Here's what I got so far:

import com.siemens.mp.color_game.*;
import javax.microedition.lcdui.*;

public class ExampleGameCanvas extends GameCanvas implements Runnable {
//Other variables
private int gameAction = 0;
private int keyCodes = 0;

//necessary objects declared here.

// Sprites to be used
private GreenThing playerSprite;
private Sprite backgroundSprite;

// Layer Manager
private LayerManager layerManager;

// Constructor and initialization
public ExampleGameCanvas() throws Exception {
super(true);
//Sprite creation and BG code goes here.

}

// Automatically start thread for game loop
public void start() {
isPlay = true;
Thread t = new Thread(this);
t.start();
}

public void stop() { isPlay = false; }

// Main Game Loop
public void run() {
Graphics g = getGraphics();
while (isPlay == true) {

switch(gameAction){
case UP:
playerSprite.moveUp();
break;
case DOWN:
playerSprite.moveDown();
break;
case LEFT:
playerSprite.moveLeft();
break;
case RIGHT:
playerSprite.moveRight();
break;
case 0:
switch(keyCodes){
case KEY_NUM1:
playerSprite.moveUpLeft();
break;
case KEY_NUM3:
playerSprite.moveUpRight();
break;
case KEY_NUM7:
playerSprite.moveDownLeft();
break;
case KEY_NUM9:
playerSprite.moveDownRight();
break;
}
break;
}
drawScreen(g);
try { Thread.sleep(delay); }
catch (InterruptedException ie) {}

}
}

// Method to Handle User Inputs

.
.
.
public void keyPressed(int keyCode){
super.keyPressed(keyCode);
gameAction = 0;
gameAction = getGameAction(keyCode);
//keyCodes = getKeyCode(keyCode);
}
public void keyReleased(int keyCode){
super.keyReleased(keyCode);
gameAction = 0;
}


// Method to Display Graphics


}

[Edited by - thelegendofzaku on December 1, 2005 11:03:52 PM]

Share this post


Link to post
Share on other sites
spillz    122
dude, help me help you by only posting the relevant code (most importantly delete out all the commented stuff)

as far as i can see you have implemented keyPressed, which you use to set your gameAction and keyCode variables to the code of the last key pressed (i think you should get rid of gameAction and just test keyCode against KEY_NUM2,4,6,8 for the horizontal/vertical movements in your main loop as the gameAction codes for up, left, right and down aren't 2,4,6,8 keys on all phones). the biggest problem with the code is your sprite will keep moving until another key is pressed unless you override keyReleased and inside that set the gameAction to zero OR set your gameAction to zero after each move (if you do it the 2nd way the user will need a key press for each move).

is this going to be a shooting game? will you have a fire key? if so, you are going to need to keep track of more than one key at a time. that is a user may want to move and fire at the same time. i'm not sure how well the keyPressed/keyReleased methods actually handle multiple keys being pressed at the same time... (some people just have automatic fire in their games because of this)

Share this post


Link to post
Share on other sites
Quote:
Original post by spillz
dude, help me help you by only posting the relevant code (most importantly delete out all the commented stuff)

as far as i can see you have implemented keyPressed, which you use to set your gameAction and keyCode variables to the code of the last key pressed (i think you should get rid of gameAction and just test keyCode against KEY_NUM2,4,6,8 for the horizontal/vertical movements in your main loop as the gameAction codes for up, left, right and down aren't 2,4,6,8 keys on all phones). the biggest problem with the code is your sprite will keep moving until another key is pressed unless you override keyReleased and inside that set the gameAction to zero OR set your gameAction to zero after each move (if you do it the 2nd way the user will need a key press for each move).

is this going to be a shooting game? will you have a fire key? if so, you are going to need to keep track of more than one key at a time. that is a user may want to move and fire at the same time. i'm not sure how well the keyPressed/keyReleased methods actually handle multiple keys being pressed at the same time... (some people just have automatic fire in their games because of this)


Sorry about that, it's all cleaned up now. Anyways, yes it will be a shooter game, and I will most likely lean towards autofire. As I probably said before, the Siemens M55/56 has a way of automapping d-pad game actions to 2, 4, 6, 8, and I'm not too worried about this not being on all phones since the game will only be built for my phone. I will try to map to the number keys only.

EDIT: Every time I use the get getKeyCode method and test it against 2, 4, 6, 8, I get an IllegalArgumentException. In other words, since the number keys already have key codes, how do I invoke them? Also, after setting gameAction to zero in both keyPressed and keyReleased methods, I was able to move the sprite according to how long I held a key. Anyways, here's what I had in mind:

private int keyUpLeft=49;
private int keyUpRight=51;
private int keyDownLeft=55;
private int keyDownRight=57;

public void run(){
switch(keyUpLeft){
case KEY_NUM1:
playerSprite.moveUpLeft();
break;
}
switch(keyUpRight){
case KEY_NUM3:
playerSprite.moveUpRight();
break;
}
switch(keyDownLeft){
case KEY_NUM7:
playerSprite.moveDownLeft();
break;
}
switch(keyDownRight){
case KEY_NUM9:
playerSprite.moveDownRight();
break;
}
}

[Edited by - thelegendofzaku on December 1, 2005 11:25:06 PM]

Share this post


Link to post
Share on other sites
spillz    122
Quote:
Original post by thelegendofzaku
EDIT: Every time I use the get getKeyCode method and test it against 2, 4, 6, 8, I get an IllegalArgumentException. In other words, since the number keys already have key codes, how do I invoke them? Also, after setting gameAction to zero in both keyPressed and keyReleased methods, I was able to move the sprite according to how long I held a key. Anyways, here's what I had in mind:
}


show me the relevant code for your overrides of keyPressed and keyReleased. i saw before you were using keyCodes and also saw that you posted on the j2me forum. as that poster said you don't need to use the getKeyCode method since the keyCode passed in keypressed and keyreleased is already a code in the correct format. i also think your switch statements in your main loop don't make sense.

anyway, this is what i suggest:

class YourGameCanvas extends GameCanvas implements Runnable{

int keyPress;

protected void keyPressed(int keyCode){
keyPress=keyCode;
}

protected void keyRepeated(int keyCode){
keyPress=keyCode;
}

protected void keyReleased(int keyCode){
keyPress=0;
}

public void run(){
while(/*game running condition satisfied*/)
{
switch(keyPress){
case KEY_NUM1: playerSprite.moveDownLeft(); break;
case KEY_NUM2: playerSprite.moveDown(); break;
case KEY_NUM3: playerSprite.moveDownRight(); break;
case KEY_NUM4: playerSprite.moveLeft(); break;
case KEY_NUM6: playerSprite.moveRight(); break;
case KEY_NUM7: playerSprite.moveUpLeft(); break;
case KEY_NUM8: playerSprite.moveUp(); break;
case KEY_NUM9: playerSprite.moveUpRight(); break;
}
}
//drawing+flushgraphics + sleep etc...
}
}

if you insist on processing the left/right/up/down keys as game actions then delete the lines for KEY_NUM2,4,6,8 and add an additional switch statement straight after the one above:

switch(getGameAction(keyPress)){
case DOWN: playerSprite.moveDown(); break;
case LEFT: playerSprite.moveLeft(); break;
case RIGHT: playerSprite.moveRight(); break;
case UP: playerSprite.moveUp(); break;
}

Share this post


Link to post
Share on other sites
I just finished trying that method, but it made it worst since that is preventing the screen from being drawn. Here's some recent code:

public class ExampleGameCanvas extends GameCanvas implements Runnable {
int keyPress;

//more declared variables

protected void keyPressed(int keyCode){
//super.keyPressed(keyCode);
keyPress=keyCode;
}
protected void keyRepeated(int keyCode){
//super.keyRepeated(keyCode);
keyPress=keyCode;
}
protected void keyReleased(int keyCode){
//super.keyReleased(keyCode);
keyPress=0;
}
// Constructor and initialization
public ExampleGameCanvas() throws Exception {
super(false);
//usual constructor code goes here

}
.
.
.
.

// Main Game Loop
public void run() {
Graphics g = getGraphics();
while (isPlay == true) {
switch(keyPress){
case KEY_NUM1: playerSprite.moveUpLeft(); break;
case KEY_NUM2: playerSprite.moveUp(); break;
case KEY_NUM3: playerSprite.moveUpRight(); break;
case KEY_NUM4: playerSprite.moveLeft(); break;
case KEY_NUM6: playerSprite.moveRight(); break;
case KEY_NUM7: playerSprite.moveDownLeft(); break;
case KEY_NUM8: playerSprite.moveDown(); break;
case KEY_NUM9: playerSprite.moveDownRight(); break;
}
switch(getGameAction(keyPress)){
case UP: playerSprite.moveUp(); break;
case DOWN: playerSprite.moveDown(); break;
case LEFT: playerSprite.moveLeft(); break;
case RIGHT: playerSprite.moveRight(); break;
}
drawScreen(g);
try { Thread.sleep(delay); }
catch (InterruptedException ie) {}
}
}
// Method to Display Graphics
private void drawScreen(Graphics g) {
//usual drawing of the graphics
}

}

Share this post


Link to post
Share on other sites
spillz    122
first you may as well call the super versions of keyPressed/keyRepeated/keyReleased in case there is something there that affects screen painting (i really doubt it)...

i don't know why anything there would stop the screen from drawing. paste in your draw code. are you sure you haven't misplaced a brace somewhere? try commenting out the switches statments/method overrides and see if your drawing comes back. then add back incrementally till you find the problem. if you are using netbeans, use the debugger to step through your code in the emulator.

Share this post


Link to post
Share on other sites
OK, I figured out the problem. It seems that the Up, Down, Left, and Right cases in the threaded loop did not like the key code variable. So what I did was seperate them into two different int variables: one invoking the getGameAction method for the D-Pad and 2, 4, 6, 8 while keeping keyPresses equaling to the keyCode variable in the keyPressed method. Then I just created seperate threaded loops for them: one for the game actions and another for the diagonals and it worked. Here's the almost final code since I wanna ask you something. I commented out the keyRepeated method, do I really need it for my code or can it be expended since I accomplished my main goal, which was move a sprite according to how long I held down a key and to move diagonally? Anyways, here it is:

import com.siemens.mp.color_game.*;
import javax.microedition.lcdui.*;

public class ExampleGameCanvas extends GameCanvas implements Runnable {
private boolean isPlay; // Game Loop runs when isPlay is true
private long delay; // To give thread consistency
private int currentX, currentY; // To hold current position of the 'X'
private int width; // To hold screen width
private int height; // To hold screen height
private int keyPress;
private int regGameCodes;

// Sprites to be used
private GreenThing playerSprite;
private Sprite backgroundSprite;

// Layer Manager
private LayerManager layerManager;

// Constructor and initialization
public ExampleGameCanvas() throws Exception {
super(true);
width = getWidth();
height = getHeight();

currentX = width / 2;
currentY = height / 2;
delay = 20;

// Load Images to Sprites
Image playerImage = Image.createImage("/transparent.PNG");
playerSprite = new GreenThing (playerImage,32,32,width,height);
playerSprite.startPosition();

Image backgroundImage = Image.createImage("/background2.PNG");
backgroundSprite = new Sprite(backgroundImage);

layerManager = new LayerManager();
layerManager.append(playerSprite);
layerManager.append(backgroundSprite);

}
// Automatically start thread for game loop
public void start() {
isPlay = true;
Thread t = new Thread(this);
t.start();
}
public void stop() { isPlay = false; }

// Main Game Loop
public void run() {
Graphics g = getGraphics();
while (isPlay == true) {
switch(regGameCodes){
case UP: playerSprite.moveUp(); break;
case DOWN: playerSprite.moveDown(); break;
case LEFT: playerSprite.moveLeft(); break;
case RIGHT: playerSprite.moveRight(); break;
}
switch(keyPress){
case KEY_NUM1: playerSprite.moveUpLeft(); break;
case KEY_NUM3: playerSprite.moveUpRight(); break;
case KEY_NUM7: playerSprite.moveDownLeft(); break;
case KEY_NUM9: playerSprite.moveDownRight(); break;
}
drawScreen(g);
try { Thread.sleep(delay); }
catch (InterruptedException ie) {}
}
}
protected void keyPressed(int keyCode){
super.keyPressed(keyCode);
regGameCodes=getGameAction(keyCode);
keyPress=keyCode;
}
/*protected void keyRepeated(int keyCode){
super.keyRepeated(keyCode);
keyPress=keyCode;
}*/
protected void keyReleased(int keyCode){
super.keyReleased(keyCode);
regGameCodes=0;
keyPress=0;
}
// Method to Display Graphics
private void drawScreen(Graphics g) {
//g.setColor(0x00C000);
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0x0000ff);

// updating player sprite position
//playerSprite.setPosition(currentX,currentY);

// display all layers
//layerManager.paint(g,0,0);
layerManager.setViewWindow(0,0,101,80);
layerManager.paint(g,0,0);
flushGraphics();
}
}

Thank you very much in advance if the code is good to go.

Share this post


Link to post
Share on other sites
spillz    122
that looks like it should work fine. you probably don't need keyRepeated for your purposes.

the UP, DOWN, LEFT, RIGHT members of GameCanvas aren't standard keyCodes: that's why you need to call getGameAction(keyPress). i don't know why there would be a difference between calling getGameAction in the keyPressed method (in this version) or in the switch statement (as in the previous version of your code).

good luck with your game

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this