I'm following video tutorials using libGdx and kinda adapting the code to get what I want. I managed to draw the HUD and an animation, but when I tried to add a button following another tutorial I ended with a black screen, here is the code:
public class WorldRenderer implements ApplicationListener, Screen {
Pixmap pixmap;
SpriteBatch batch;
private Skin skin;
private Stage stage;
OrthographicCamera cam;
Table table;
TextButton buttonShake;
BitmapFont white;
BitmapFont black;
private TextureAtlas atlas;
Texture virusTexture;
Texture hudTexture;
private static final float HUD_WIDTH = 480f;
private static final float HUD_HEIGHT = 800f;
private static final float WALK_ANIM_WIDTH = 337f;
private static final float WALK_ANIM_HEIGHT = 213f;
public static final int SCREEN_WIDTH = 480;
public static final int SCREEN_HEIGHT = 800;
private static final int FRAME_COLS = 2;
private static final int FRAME_ROWS = 2;
Animation walkAnimation;
Texture walkSheet;
TextureRegion[] walkFrames;
TextureRegion currentFrame;
float stateTime;
float width, height;
Hud hud;
public WorldRenderer(World world) {
this.world = world;
walkSheet = new Texture("data/character.png");
TextureRegion[][] tmp = TextureRegion.split(walkSheet,
walkSheet.getWidth() / FRAME_COLS, walkSheet.getHeight()
/ FRAME_ROWS);
walkFrames = new TextureRegion[FRAME_COLS * FRAME_ROWS];
int index = 0;
for (int i = 0; i < FRAME_ROWS; i++) {
for (int j = 0; j < FRAME_COLS; j++) {
walkFrames[index++] = tmp[i][j];
}
}
walkAnimation = new Animation(0.25f, walkFrames);
stateTime = 0f;
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
cam = new OrthographicCamera(SCREEN_WIDTH, SCREEN_HEIGHT);
cam.position.set(SCREEN_WIDTH / 2f, SCREEN_HEIGHT / 2f, 0);
batch = new SpriteBatch();
batch.setProjectionMatrix(cam.combined);
hudTexture = new Texture("data/hud.png");
hudTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.update();
stage.act(delta);
stateTime += Gdx.graphics.getDeltaTime();
currentFrame = walkAnimation.getKeyFrame(stateTime, true);
hud = world.getHud();
batch.setProjectionMatrix(cam.combined);
batch.begin();
batch.draw(hudTexture, SCREEN_WIDTH / 2f - HUD_WIDTH / 2f,
SCREEN_HEIGHT / 2f - HUD_HEIGHT / 2f);
batch.draw(currentFrame, SCREEN_WIDTH / 2f - WALK_ANIM_WIDTH / 2f,
SCREEN_HEIGHT / 2f - WALK_ANIM_HEIGHT / 2f);
batch.end();
stage.draw();
}
public void dispose() {
batch.dispose();
virusTexture.dispose();
hudTexture.dispose();
skin.dispose();
atlas.dispose();
white.dispose();
black.dispose();
stage.dispose();
}
#Override
public void create() {
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void show() {
}
#Override
public void hide() {
// TODO Auto-generated method stub
}
#Override
public void render() {
// TODO Auto-generated method stub
}
I'm pretty sure you are also going to find unused lines of code so please point them out for me if you want so I can better organize my code and learn. Thanks!
One possible problem is that you implement an ApplicationListener and a Screen, both have the render method and almost the same methods.
You shouldn't implement both. If this is your app entry-point I would go for the applicationListener.
However, for the educational purpose you should read some topics about the Game class as your next app entry-point. Consider using separated "screens" that handle the render part and a "brain" that allows them to communicate with each other, this is where the Game class becomes handy.
Things can seem a little trickier at the beginning but will definitely make sense to you after a bit of studying ;)
Related
My sprite moves too slowly. Basically I want to apply less force to move my player. Current this:
getBody().applyForceToCenter(new Vector2(-10000000f,0f), true);
is the force needed to make it move a tiny bit.
I know the reason why I am not able to move it is since I haven't scaled the sprite (64x64) it weights more than 400kg. What should be the correct scale?
This is my game screen.
public class GameScreen implements Screen {
//Reference to our Game, used to set Screens
private Logang game;
//basic playscreen variables
private OrthographicCamera gamecam;
private Viewport gamePort;
//Box2d variables
private World world;
private Box2DDebugRenderer b2dr;
boolean drawn = true;
private Player p;
private int pX = 100, pY = 300;
public GameScreen(Logang game) {
this.game = game;
//create cam used to follow mario through cam world
gamecam = new OrthographicCamera();
gamePort = new ScalingViewport(Scaling.stretch, Logang.GWIDTH, Logang.GHEIGHT, gamecam);
gamePort.apply();
gamecam.position.set(gamecam.viewportWidth / 2, gamecam.viewportHeight / 2, 0);
gamecam.update();
Box2D.init();
//create our Box2D world, setting no gravity in X, -10 gravity in Y, and allow bodies to sleep
world = new World(new Vector2(0, Logang.GRAVITY), true);
//allows for debug lines of our box2d world.
b2dr = new Box2DDebugRenderer();
//create a FitViewport to maintain virtual aspect ratio despite screen size
p = new Player(new Sprite(new Texture("hud_p3.png")), world, pX, pY, 1);
//initially set our gamcam to be centered correctly at the start of of map
line();
}
#Override
public void show() {
}
public void update(float dt) {
//handle user input first
p.update(dt);
//update our gamecam with correct coordinates after changes
}
#Override
public void render(float delta) {
//separate our update logic from render
update(delta);
//Clear the game screen with Black
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
world.step(1f / 60f, 6, 2);
gamecam.position.set(p.getSprite().getX(),Logang.GHEIGHT / 2, 0); // x and y could be changed by Keyboard input for example
gamecam.update();
game.getBatch().setProjectionMatrix(gamecam.combined);
//renderer our Box2DDebugLines
b2dr.render(world, gamecam.combined);
System.out.println("Player x: " + p.getSprite().getX() + " Camera X: " + gamecam.position.x + " Body X: " + p.getBody().getPosition().x);
//System.out.println("Player y: " + p.getSprite().getY() + " Camera Y: " + gamecam.position.y + " Body Y: " + p.getBody().getPosition().y);
game.getBatch().begin();
if (p.getBody() != null)
p.render(game.getBatch());
EntityManager.renderTerra(game.getBatch(), delta);
game.getBatch().end();
}
public void line() {
Texture tmp = new Texture("hud_p3.png");
tmp.setWrap(Texture.TextureWrap.MirroredRepeat, Texture.TextureWrap.MirroredRepeat);
for (int i = 0; i < 50; i++) {
EntityManager.add(new Ground(new Sprite(tmp), world, (int)(i * Logang.TILE), 1, 2));
}
// EntityManager.changeSize(((Logang.TILE) * 5),Logang.TILE);
}
#Override
public void resize(int width, int height) {
//updated our game viewport
gamePort.update(width, height);
gamecam.position.set(gamecam.viewportWidth / 2, gamecam.viewportHeight / 2, 0);
}
public World getWorld() {
return world;
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
world.dispose();
b2dr.dispose();
}
And this is my entity class
private World world;
private Sprite sprite;
private Body body;
private int tipo;
public Entity(Sprite sprite, World world, int x, int y, int tipo) {
this.sprite = sprite;
this.world = world;
getSprite().setPosition(x, y);
sprite.setSize(Logang.TILE, Logang.TILE);
sprite.setOriginCenter();
define(tipo);
this.tipo = tipo;
}
public void update(float dt){
if(Gdx.input.isKeyPressed(Input.Keys.LEFT)){
getBody().applyForceToCenter(new Vector2(-10000000f,0f), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)){
getBody().applyForceToCenter(new Vector2(10000000f,0f), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.SPACE)){
//getBody().applyLinearImpulse(0f,-Logang.GRAVITY,
getBody().getPosition().x, getBody().getPosition().y, true);
}
}
public void define(int tipo) {
BodyDef bdef = new BodyDef();
bdef.position.set((getSprite().getX() + getSprite().getWidth() / 2),
(getSprite().getY() + getSprite().getHeight() / 2));
switch (tipo) {
case 1: {
bdef.type = BodyDef.BodyType.DynamicBody;
break;
}
case 2: {
bdef.type = BodyDef.BodyType.StaticBody;
break;
}
case 3: {
bdef.type = BodyDef.BodyType.DynamicBody;
break;
}
}
body = world.createBody(bdef);
FixtureDef fdef = new FixtureDef();
fdef.density=0.001f; // (weight: range 0.01 to 1 is good)
PolygonShape shape = new PolygonShape();
shape.setAsBox(getSprite().getWidth() / 2, getSprite().getHeight() / 2);
fdef.shape = shape;
body.createFixture(fdef);
body.setUserData(this);
shape.dispose();
}
public void render(SpriteBatch batch) {
if (tipo != 2) {
float posX = getBody().getPosition().x;
float posY = getBody().getPosition().y;
getSprite().setPosition(posX - getSprite().getWidth() / 2, posY -
getSprite().getHeight() / 2);
}
getSprite().draw(batch);
}
public Sprite getSprite() {
return sprite;
}
public void setSprite(Sprite sprite) {
this.sprite = sprite;
}
public Body getBody() {
return body;
}
public void setBody(Body body) {
this.body = body;
}
And this are the in game variables
public static final int GWIDTH = 800;
public static final int GHEIGHT = (GWIDTH/16)*9;
public static final float PPM = 100f;
public static final float GRAVITY = -10f;
public static final float TILE = 64;
Could you please give me a fix?
I already tried to divide body and gamecam position still no effect
What should be the correct scale?
The right scale would be the scale in real life where 1 unit in LibGDX (Box2D) represents 1 meter in real life. I always advice people to use this scale and zoom the camera properly.
Mind though, if you are using very large objects and zoom the camera all the way back objects appear to be falling slowly. This is obviously because your camera contains a much larger space. Not only would it fall slowly but it won't interact properly with the world if the item is supposed to be smaller.
Adept the camera to your world, not your world to your camera.
More detailed answer I gave
I'm learning libgdx and currently doing a flappy bird demo. For fun I tried to implement when the score reached a certain number the bird sprite texture will update and change to another color. Instead of using the spritebatch and changing the color through tinting I wanted to create a new texture(png file).
The problem is since it is a sprite it needs to be animated so the wings will flap. When I try and update the texture at runtime it will only work but the animation wont play.
Here is my bird class:
public class Bird {
private static final int GRAVITY = -15;
private static final int MOVEMENT = 100;
private Vector3 position;
private Vector3 velocity;
private Rectangle bounds;
private Animation birdAnimation;
private Texture birdTexture;
private TextureRegion textureRegion;
private Sound flap;
public Bird(int x, int y){
position = new Vector3(x, y, 0);
velocity = new Vector3(0, 0, 0);
textureRegion = new TextureRegion(returnTexture());
birdAnimation = new Animation(textureRegion, 3, 0.5f);
bounds = new Rectangle(x, y, returnTexture().getWidth() / 3, returnTexture().getHeight());
flap = Gdx.audio.newSound(Gdx.files.internal("sfx_wing.ogg"));
}
public void update(float dt){
textureRegion = new TextureRegion(returnTexture());
birdAnimation = new Animation(textureRegion, 3, 0.5f);
birdAnimation.update(dt);
if(position.y > 0){
velocity.add(0, GRAVITY, 0);
}
velocity.scl(dt);
position.add(MOVEMENT * dt, velocity.y, 0);
if(position.y < 0){
position.y = 0;
}
velocity.scl(1/dt);
bounds.setPosition(position.x, position.y);
}
public TextureRegion getTexture() {
return birdAnimation.getFrame();
}
public Texture returnTexture(){
if(PlayState.score > 1){
return birdTexture = new Texture("birdanimation1.png");
}else{
return birdTexture = new Texture("birdanimation.png");
}
}
public Vector3 getPosition() {
return position;
}
public void jump(){
velocity.y = 250;
flap.play(0.15f);
}
public Rectangle getBounds(){
return bounds;
}
public void dispose(){
returnTexture().dispose();
flap.dispose();
}
}
Here is my animation class:
public class Animation {
private Array<TextureRegion> frames;
private float maxFrameTime;
private float currentFrameTime;
private int frameCount;
private int frame;
public Animation(TextureRegion region, int frameCount, float cycleTime){
frames = new Array<TextureRegion>();
int frameWidth = region.getRegionWidth() / frameCount;
for(int i = 0; i < frameCount; i++){
frames.add(new TextureRegion(region, i * frameWidth, 0, frameWidth, region.getRegionHeight()));
}
this.frameCount = frameCount;
maxFrameTime = cycleTime / frameCount;
frame = 0;
}
public void update(float dt){
currentFrameTime += dt;
if(currentFrameTime > maxFrameTime){
frame++;
currentFrameTime = 0;
}
if(frame >= frameCount){
frame = 0;
}
}
public TextureRegion getFrame(){
return frames.get(frame);
}
}
Here's my render code in my play state:
#Override
public void render(SpriteBatch sb) {
sb.setProjectionMatrix(cam.combined);
sb.begin();
sb.draw(bg, cam.position.x - (cam.viewportWidth /2), 0);
sb.draw(bird.getTexture(), bird.getPosition().x, bird.getPosition().y);
for(Tube tube : tubes){
sb.draw(tube.getTopTube(), tube.getPosTopTube().x, tube.getPosTopTube().y);
sb.draw(tube.getBottomTube(), tube.getPosBotTube().x, tube.getPosBotTube().y);
}
sb.draw(ground, groundPos1.x, groundPos1.y);
sb.draw(ground, groundPos2.x, groundPos2.y);
font.draw(sb, text, cam.position.x - gl.width / 2, cam.position.y + 200);
sb.end();
}
If you need any other classes just ask. I'm probably making a stupid mistake or just coding it entirely wrong for what I'm trying to achieve.
Thanks, Jackson
You need to NOT load your textures every single frame.
libgdx has its own Animation class so you don't need to make your own.
Here is an example on animation from libgdx's github:
2D Animation
To make it simple, just have 2 animaitons on Bird and switch between them when you need to.
I am having a problem with the libGDX engine. I have a pair of variables to set the width and height of the screen, and another pair to set the width and height of the camera. However, whenever the camera is not the same size as the screen, the engine will not render any sprites. Here is my code for you to look at:
My Constants:
public abstract class Constants
{
public static final int WINDOW_WIDTH = 800;
public static final int WINDOW_HEIGHT = 600;
public static final int CAMERA_WIDTH = 800;
public static final int CAMERA_HEIGHT = 600;
}
Where the Sprite is Created:
public class GameController
{
public static final String TAG = GameController.class.getName();
public Sprite[] testSprites;
public int selectedSprite;
public GameController()
{
init();
}
private void init()
{
initTestObjects();
}
private void initTestObjects()
{
testSprites = new Sprite[1];
int width = 32;
int height = 32;
Pixmap pixmap = createProceduralPixmap(width, height);
Texture texture = new Texture(pixmap);
for(int i = 0; i < testSprites.length; i++)
{
Sprite spr = new Sprite(texture);
spr.setSize(32, 32);
spr.setOrigin(spr.getWidth() / 2.0f, spr.getHeight() / 2.0f);
spr.setPosition(0, 0);
testSprites[i] = spr;
}
selectedSprite = 0;
}
private Pixmap createProceduralPixmap(int width, int height)
{
Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);
pixmap.setColor(1, 0, 0, 0.5f);
pixmap.fill();
pixmap.setColor(1, 1, 0, 1);
pixmap.drawLine(0, 0, width, height);
pixmap.drawLine(width, 0, 0, height);
pixmap.setColor(0, 1, 1, 1);
pixmap.drawRectangle(0, 0, width, height);
return pixmap;
}
public void update(float deltaTime)
{
updateTestObjects(deltaTime);
}
public void updateTestObjects(float deltaTime)
{
float rotation = testSprites[selectedSprite].getRotation();
rotation += 90 * deltaTime;
rotation %= 360;
testSprites[selectedSprite].setRotation(rotation);
}
}
And the Drawing:
public class GameRenderer implements Disposable
{
public static final String TAG = GameRenderer.class.getName();
private OrthographicCamera camera;
private SpriteBatch batch;
private GameController gameController;
public GameRenderer(GameController gameController)
{
this.gameController = gameController;
init();
}
private void init()
{
batch = new SpriteBatch();
camera = new OrthographicCamera(Constants.CAMERA_WIDTH,
Constants.CAMERA_HEIGHT);
camera.position.set(0, 0, 0);
camera.update();
}
public void render()
{
renderTestObjects();
}
private void renderTestObjects()
{
batch.setProjectionMatrix(camera.combined);
batch.begin();
for(Sprite sprite : gameController.testSprites)
{
sprite.draw(batch);
}
batch.end();
}
public void resize(int width, int height)
{
camera.viewportWidth =
(Constants.CAMERA_HEIGHT / height) * width;
camera.update();
}
#Override
public void dispose()
{
batch.dispose();
}
}
The code above works fine and will render a sprite in the center of the screen, but when I change CAMERA_WIDTH and CAMERA_HEIGHT to say 80 and 60 respectively, instead of making the sprite 10x larger like it should, it doesn't draw anything. Any advice?
You are dividing ints by ints, so you end up with poorly scaled numbers. For example:
camera.viewportWidth =
(Constants.CAMERA_HEIGHT / height) * width;
If the screen width and height are 800 and 600, and CAMERA_HEIGHT is 60, then the equation becomes
camera.viewportWidth =
(60 / 600) * 800;
which becomes (due to pure integer math):
camera.viewportWidth =
(0) * 800;
which becomes 0. You need to cast those ints to floats before dividing! This may not be the only issue, but it alone could be causing your problem.
Player Class
public class Player extends Sprite implements InputProcessor {
public Vector2 velocity = new Vector2();
private float speed = 500;
public Rectangle rectangle;
public Player(Sprite sprite){
super(sprite);
this.rectangle = sprite.getBoundingRectangle();
}
public void draw(SpriteBatch spriteBatch){
update(Gdx.graphics.getDeltaTime());
super.draw(spriteBatch);
}
public void update(float delta) {
rectangle = new Rectangle(getX() + velocity.x * delta,0,rectangle.getWidth(),rectangle.getWidth());
setX(getX() + velocity.x * delta);
}
}
PlayScreen Class
public class PlayScreen implements Screen {
private Player player;
private OrthographicCamera camera;
private OrthogonalTiledMapRenderer renderer;
private TiledMap map;
private Rectangle rightRectangle, leftRectangle, playerRectangle;
//private ShapeRenderer shapeRenderer;
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
renderer.render();
renderer.getSpriteBatch().begin();
player.draw(renderer.getSpriteBatch());
renderer.getSpriteBatch().end();
//shapeRenderer.begin(ShapeType.Filled);
//shapeRenderer.setColor(0, 1, 0, 1);
//shapeRenderer.rect(
// player.getX() + player.velocity.x * delta, 0,
// player.rectangle.getWidth(), player.rectangle.getHeight());
//shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
}
#Override
public void show() {
camera = new OrthographicCamera();
map = new TiledMap();
renderer = new OrthogonalTiledMapRenderer(map);
//shapeRenderer = new ShapeRenderer();
player = new Player(new Sprite(new Texture("img/player.png")));
rightRectangle = new Rectangle(1280,0,0,720);
leftRectangle = new Rectangle(0,0,0,720);
boolean wallLeft = leftRectangle.overlaps(player.rectangle);
boolean wallRight = rightRectangle.overlaps(player.rectangle);
if(wallLeft){
System.out.println("wallLeft Overlap");
player.velocity.x = 0;
}
else if(wallRight){
System.out.println("wallRight Overlap");
player.velocity.x = 0;
}
player.setPosition(
Gdx.graphics.getWidth()/2f - player.getWidth()/2f,
Gdx.graphics.getHeight()/2f - player.getHeight()/2f
- Gdx.graphics.getHeight()/5f);
}
}
Doesn't seem to be colliding correctly. The rightRectangle and leftRectangle are my screen side bounds. When I use the shapeRenderer, it produces the ShapeRendered rectangle and it will follow my player around. However, I believe that my player.rectangle is not moving at all for some reason, resulting in it not colliding with my side bounds. Any help would be greatly appreciated!
rightRectangle = new Rectangle(1280,0,0,720);
leftRectangle = new Rectangle(0,0,0,720);
A Rectangle is defined as Rectangle(x, y, width, height). It looks like you are trying to define it incorrectly as Rectangle(x1, y1, x2, y2). In the above, you have created two rectangles of 0 width.
I tried everything I could, but my libgdx application won't scale down on android 2.2. It displays a full image of 640x480 on a screen of only 480x320. Here's the code I got so far;
private static final int VIRTUAL_WIDTH = 640;
private static final int VIRTUAL_HEIGHT = 480;
private static final float ASPECT_RATIO = (float)VIRTUAL_WIDTH/(float)VIRTUAL_HEIGHT;
public static Texture BMP_BACKGROUND;
public static Rectangle viewport;
public static OrthographicCamera camera;
public static SpriteBatch batch;
public void create()
{
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
BMP_BACKGROUND = new Texture(Gdx.files.internal("data/bmpSpaceBackground.png"));
BMP_BACKGROUND.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
batch = new SpriteBatch();
camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);
}
#Override
public void dispose()
{
batch.dispose();
BMP_BACKGROUND.dispose();
}
#Override
public void render()
{
camera.update();
camera.apply(Gdx.gl10);
// set viewport
Gdx.gl.glViewport((int) viewport.x, (int) viewport.y,
(int) viewport.width, (int) viewport.height);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
batch.draw(BMP_BACKGROUND, 0, 0);
Game.run();
}
public void resize(int width, int height)
{
float aspectRatio = (float)width/(float)height;
float scale = 1f;
Vector2 crop = new Vector2(0f, 0f);
if(aspectRatio > ASPECT_RATIO)
{
scale = (float)height/(float)VIRTUAL_HEIGHT;
crop.x = (width - VIRTUAL_WIDTH*scale)/2f;
}
else if(aspectRatio < ASPECT_RATIO)
{
scale = (float)width/(float)VIRTUAL_WIDTH;
crop.y = (height - VIRTUAL_HEIGHT*scale)/2f;
}
else
{
scale = (float)width/(float)VIRTUAL_WIDTH;
}
float w = (float)VIRTUAL_WIDTH*scale;
float h = (float)VIRTUAL_HEIGHT*scale;
viewport = new Rectangle((int)crop.x, (int)crop.y, (int)w, (int)h);
}
Anyone can tell me what I'm doing wrong?
Try this
public void resize(int width, int height)
{
cam.viewportHeight = height; //set the viewport
cam.viewportWidth = width;
if (Config.VIRTUAL_VIEW_WIDTH / cam.viewportWidth < Config.VIRTUAL_VIEW_HEIGHT
/ cam.viewportHeight) {
//sett the right zoom direct
cam.zoom = Config.VIRTUAL_VIEW_HEIGHT / cam.viewportHeight;
} else {
//sett the right zoom direct
cam.zoom = Config.VIRTUAL_VIEW_WIDTH / cam.viewportWidth;
}
cam.update();
}
This does work and i can work with the virutal resolution. But dont forget to set the camera of your stage you are using! Else it does have no effect!
stage.setCamera(camera).
if you have done that you can work with the virtual resolution inside of your stage.
regards
Your code is 1:1 from an example online.