Player doesn't move when attempting to draw from a Player class - java

So I'm currently in the process of refactoring my code in my game for the player and started a player class:
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class Player
{
protected String name;
protected float health, maxHealth;
protected Texture texture;
protected int xPos, yPos;
public Player(String name, float maxHealth, Texture texture) {
this(name, maxHealth, texture, 0, 0);
}
public Player(String name, float maxHealth, Texture texture, int xPos, int yPos) {
this.name = name;
this.health = this.maxHealth = maxHealth;
this.texture = texture;
this.xPos = xPos;
this.yPos = yPos;
}
public Texture getTexture() {
return this.texture;
}
public int getX() {
return xPos;
}
public int getY() {
return yPos;
}
public String getName() {
return name;
}
public void draw(SpriteBatch spriteBatch) {
spriteBatch.begin();
spriteBatch.draw(texture, xPos, yPos, 0.5f, 0.5f, 0, 0,
texture.getWidth(), texture.getHeight(), false, false);
spriteBatch.end();
}
public void update(float delta) {
processMovement(delta);
}
public void processMovement(float delta) {
if(Gdx.input.isKeyPressed(Input.Keys.A)) {
xPos -= 50 * delta;
}
if(Gdx.input.isKeyPressed(Input.Keys.D)) {
xPos += 50 * delta;
}
}
}
I'm using an orthographic camera, I haven't added any terrain yet as I'm going to do that next, however I want the player to always stay in the center but have the player move around the terrain when I draw it.
The code I have for creating the camera and drawing my player is as follows:
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class GameState implements Screen
{
private PixelGame parent;
private OrthographicCamera camera;
private Player player;
private Texture playerTexture;
private SpriteBatch spriteBatch;
public GameState(PixelGame parent) {
this.parent = parent;
this.playerTexture = new Texture(Gdx.files.internal("player/sprite.png"));
this.player = new Player("Me", 20, playerTexture);
this.spriteBatch = new SpriteBatch();
}
#Override
public void render(float delta) {
camera.update();
spriteBatch.setProjectionMatrix(camera.combined);
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
player.draw(spriteBatch);
player.update(delta);
}
#Override
public void show() {
}
#Override
public void dispose() {
}
#Override
public void resize(int width, int height) {
float ratio = (float)width / (float)height;
camera = new OrthographicCamera(2f * ratio, 2f);
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
}
The player sprite doesn't seem to move, but when i change the values to something >75 the player sprite sprints across the screen like no-ones business.

xPos and yPos in your class are ints. I would say that's the problem. Your processMovement() method is called like 100 times per second and 50 * delta is most likely smaller than 1 so it's rounded to 0 because value has to be stored in int variable. Try changing xPos and yPos to floats.
And if that doesn't help (can't be sure without trying out the code) do some debugging. Put break points. See if processMovement() is called at all and if it is then what value variable delta has. How calculation goes.

Related

Stage camera won't move

In my game, I have my objects represented as a actors, thus all of the game objects would be on a Stage. For some reason when I try to move the Stage's camera around, it won't work, or actually it doesn't seem to work. I have added a game Actor to the location of 0,0. When I translate the camera's position around, the Actor still stays at the bottom left corner, despite when I log the camera's position, it shows that the camera has moved.
public class Striker extends Actor {
private Sprite img;
private World worldRef;
private Body body;
//constructor
public Striker(float size, float x, float y, World world) {
img = new Sprite(new Texture(Gdx.files.internal("Striker.png")));
//mains the aspect size ratio
img.setSize((275f / 300f) * size, size);
img.setPosition(x, y);
worldRef = world;
//set up the physics
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(x,y);
body = world.createBody(bodyDef);
}
#Override
public void draw(Batch batch, float parentAlpha) {
img.draw(batch);
}
#Override
public void act(float delta) {
super.act(delta);
}
#Override
public float getX() {
return body.getPosition().x;
}
#Override
public float getY() {
return body.getPosition().y;
}
#Override
public float getWidth() {
return img.getWidth();
}
#Override
public float getHeight() {
return img.getHeight();
}
}
The results of the 2 logs show that the camera's positions have moved, but it doesn't look like it.
public class StrikerScreen implements Screen {
public static float WIDTH = 1920;
public static float HEIGHT = 1080;
public static float PPM = 200;
private Launcher launcherRef;
private OrthographicCamera camera;
private FitViewport viewport;
private World world;
private Box2DDebugRenderer debugRenderer;
private Striker striker;
private Stage gameStage;
//constructor
public StrikerScreen(Launcher launcher) {
launcherRef = launcher;
world = new World(new Vector2(0, -9.8f), true);
debugRenderer = new Box2DDebugRenderer();
gameStage = new Stage();
camera = (OrthographicCamera) gameStage.getCamera();
viewport = new FitViewport(WIDTH / PPM, HEIGHT / PPM, gameStage.getCamera());
viewport.apply();
gameStage.setViewport(viewport);
striker = new Striker(160f / PPM, 0, 0, world);
gameStage.addActor(striker);
gameStage.getCamera().translate(viewport.getWorldWidth() / 2f, 500f, 0);
viewport.apply();
camera.update();
Gdx.app.log("StrikerScreen.java", "Camera position: " + gameStage.getCamera().position.toString());
Gdx.app.log("StrikerScreen.java", "Camera size: " + gameStage.getCamera().viewportWidth + ", " + gameStage.getCamera().viewportHeight);
}
#Override
public void show() {
}
public void update(float delta) {
world.step(1 / 30f, 6, 2);
gameStage.act(delta);
}
#Override
public void render(float delta) {
update(delta);
debugRenderer.render(world, camera.combined);
gameStage.draw();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height, true);
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
}
}
In the code you posted, the only times you move the camera are in the StrikerScreen constructor where you explicitly translate it, and in the resize method, where you have called viewport.update(width, height, true); Passing true to viewport.update tells it to move the camera to where (0, 0) is in the bottom left of corner of the viewport. Since resize is automatically called when you set your screen to this screen, that is the most recent position you have set the camera to.

what reason is there for non-smooth 2d sprite movement in this example

Just in process of making a space invaders style game for android.
I want the player to be able to touch the screen anywhere to right or left of character (at bottom of screen) to move him that direction.
The code compiles without error and the playe does move, but
A) he's moving much slower than I expected
B) The movement is 'jittery' even though I have multiplied the movement speed by deltatime in a few different ways.
Please could someone be kind enough to take a look at my code to say where I have gone wrong? :-
package com.moneylife.stashinvaders;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class StashInvaders extends ApplicationAdapter {
GameManager gameManager;
#Override
public void create () {
gameManager = new GameManager();
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
gameManager.update();
gameManager.draw();
}
#Override
public void dispose () {
gameManager.spriteBatch.dispose();
}
}
GameManager class:
package com.moneylife.stashinvaders;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
/**
* Created by Dave on 12/08/2016.
*/
public class GameManager {
SpriteBatch spriteBatch;
Player player1;
public GameManager(){
spriteBatch = new SpriteBatch();
player1 = new Player();
}
public void update(){
player1.update();
}
public void draw(){
spriteBatch.begin();
player1.draw(spriteBatch);
spriteBatch.end();
}
}
Player class:
package com.moneylife.stashinvaders;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Vector2;
/**
* Created by Dave on 12/08/2016.
*/
public class Player {
Vector2 position;
Texture texture;
int speed = 50;
float deltaTime;
public Player(){
Gdx.input.setInputProcessor(new GestureDetector(new MyGestureDetector()));
texture = new Texture("bazookaman.png");
position = new Vector2(Gdx.graphics.getBackBufferWidth() / 2 - texture.getWidth() / 2, 0);
}
public void update(){
deltaTime = Gdx.graphics.getDeltaTime();
}
public void draw(SpriteBatch spriteBatch){
spriteBatch.draw(texture, position.x, position.y);
}
public class MyGestureDetector implements GestureDetector.GestureListener {
#Override
public boolean touchDown(float x, float y, int pointer, int button) {
return false;
}
#Override
public boolean tap(float x, float y, int count, int button) {
return false;
}
#Override
public boolean longPress(float x, float y) {
return false;
}
#Override
public boolean fling(float velocityX, float velocityY, int button) {
return false;
}
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
if (x > position.x){
position.x += speed * deltaTime;
}
if (x < position.x){
position.x -= speed * deltaTime;
}
return false;
}
#Override
public boolean panStop(float x, float y, int pointer, int button) {
return false;
}
#Override
public boolean zoom(float initialDistance, float distance) {
return false;
}
#Override
public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) {
return false;
}
#Override
public void pinchStop() {
}
}
}
Look closely at your logic.
if (x > position.x){
position.x += speed * deltaTime; //if close to touch point, position.x is now bigger than x
}
if (x < position.x){ //if we just moved right past the touch point, undo it
position.x -= speed * deltaTime;
}
Furthermore, this pan method will only be called on frames where the finger position was polled and was found to have moved. The finger position is not polled 60 times per second like your game is probably running, and movement will not always have occurred.
Instead, you should use the panning to modify a target position for your character. In the update() method you can always be moving towards that target position with some speed. It's up to you whether you should be using x or deltaX in the pan method to change your target X. Different types of gameplay.

LibGDX - GestureListener touch coords return in screen pixels, how convert to in-game world units?

How can the coords returned from GestureListener (TouchDown method for example) be converted into world-unit coords.
EG. My device screen is 1080p, my game is set up to use 540x960 (portrait).
In my current example project (code below) the paddle moves along x-axis twice as quickly as my finger, hence the position being set is given in screen-pixels.
Of course in this exact example, I could easily just divide it by 2 as I know the gameworld-units are exactly half of the device-pixels, however what if I wanted to make my world size 150x150 units --or if I was testing on a device with different screen sizes??
package com.moneylife.breakingass;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Vector2;
public class BreakingAss extends ApplicationAdapter {
SpriteBatch batch;
Sprite bgSprite, bgCoverSprite, menu1Sprite;
Paddle paddle;
enum ScreenState { StartScreen, PlayScreen, GameOverScreen }
ScreenState currentScreenState;
public int DeviceScreenWidth, DeviceScreenHeight;
public int WorldWidth = 540, WorldHeight = 960;
public OrthographicCamera camera;
int zAxis = 0;
MyGestureDetector myGestureDetector;
#Override
public void create () {
currentScreenState = ScreenState.StartScreen;
batch = new SpriteBatch();
DeviceScreenWidth = Gdx.graphics.getWidth();
DeviceScreenHeight = Gdx.graphics.getHeight();
myGestureDetector = new MyGestureDetector();
bgSprite = new Sprite(new Texture("bentoverass.jpg"));
bgCoverSprite = new Sprite(new Texture("half1080coverup.png"));
menu1Sprite = new Sprite(new Texture("menu1.jpg"));
menu1Sprite.setSize(WorldHeight * DeviceScreenWidth / DeviceScreenHeight,WorldHeight);
menu1Sprite.setPosition(0,0);
camera = new OrthographicCamera(WorldWidth, WorldHeight);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, zAxis);
camera.update();
paddle = new Paddle(WorldWidth, WorldHeight);
Gdx.input.setInputProcessor(new GestureDetector(myGestureDetector));
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
paddle.update();
camera.update();
batch.setProjectionMatrix(camera.combined);
batch.begin();
if (currentScreenState == ScreenState.StartScreen){
menu1Sprite.draw(batch);
}
if (currentScreenState == ScreenState.PlayScreen){
paddle.draw(batch);
}
batch.end();
}
#Override
public void dispose () {
batch.dispose();
}
public class MyGestureDetector implements GestureDetector.GestureListener {
#Override
public boolean touchDown(float x, float y, int pointer, int button) {
if (currentScreenState == ScreenState.StartScreen) {
if (x > 0) {
currentScreenState = ScreenState.PlayScreen;
}
}
if (currentScreenState == ScreenState.PlayScreen){
paddle.position = new Vector2(x - paddle.texture.getWidth() / 2, paddle.fixedYPosition);
}
return false;
}
#Override
public boolean tap(float x, float y, int count, int button) {
return false;
}
#Override
public boolean longPress(float x, float y) {
return false;
}
#Override
public boolean fling(float velocityX, float velocityY, int button) {
return false;
}
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
if (currentScreenState == ScreenState.PlayScreen){
paddle.position = new Vector2(x - paddle.texture.getWidth() / 2, paddle.fixedYPosition);
}
return false;
}
#Override
public boolean panStop(float x, float y, int pointer, int button) {
return false;
}
#Override
public boolean zoom(float initialDistance, float distance) {
return false;
}
#Override
public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) {
return false;
}
#Override
public void pinchStop() {
}
}
}
Getting input coordinates always yields the coordinates of the screen. Logically because the input does not (need to) know about your game world. The camera is responsible for what is being drawn to the screen and the camera can be used to transform your screen coordinates to the coordinates of your world.
//Get screen coordinates
Vector3 mousePosition = new Vector3(Gdx.input.getX, Gdx.input.getY, 0);
//Transform to world coordinates using the correct camera
camera.unproject(mousePosition);
//The mousePosition vector is changed in the above method and now contains the game world coordinates of the camera you used.
PS.
For your paddle you can make a transform method so you can use the deltaX and deltaY in the pan method. Since the delta's hand you the change in "pan"there is no need at all to transform between screen and world.

Libgdx, Android, how to drag and move a texture

currently the method I used is to detect whether touch position (Gdx.input.getX() & Y()) is in the area whether the object texture is. If so I setPosition of the object texture to the mouse position as center. While this work but it is not robust. Because if my finger move faster than the update, as soon as the touched position is outside texture bound. It won't update any more.
There must be a more reliable way and please advice. Essentially, I want to touch on the texture and drag the texture to wherever my touch is moving.
Many thanks.
My current approach is like this:
public class PlayScreen implements Screen {
int scWidth, scHeight;
int playerWidth, playerHeight;
private SpriteBatch batch; // This is in the render.
Player player;
// Create a constructor
Game game;
public PlayScreen(Game game){
this.game = game;
}
#Override
public void show() {
batch = new SpriteBatch();
scWidth = Gdx.graphics.getWidth();
scHeight = Gdx.graphics.getHeight();
playerWidth = 180;
playerHeight = 240;
player = new Player("mario.png", new Vector2(250, 300),new Vector2(playerWidth, playerHeight)) ;
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1,1,1,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
// Only draw if the mouse is hover on the image.
if (Gdx.input.getX() > player.getPosition().x && Gdx.input.getX() < player.getPosition().x + playerWidth){
if (scHeight - Gdx.input.getY() > player.getPosition().y && scHeight - Gdx.input.getY() < player.getPosition().y + playerHeight)
{
player.setPosition(new Vector2(Gdx.input.getX() - playerWidth/2,
scHeight - Gdx.input.getY() - playerHeight/2));
player.draw(batch);
} else{
player.draw(batch);
}
} else{
player.draw(batch);
}
batch.end();
player.update(); // Update the bound
}
}
Also the Player class is :
package com.jiajunyang.emosonicsgame;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
public class Player extends Image {
Vector2 position, size;
Texture player;
Rectangle bounds;
public Player(String fileName, Vector2 position, Vector2 size){
super(new Texture(Gdx.files.internal(fileName)));
this.position = position;
this.size = size;
bounds = new Rectangle(position.x, position.y, size.x, size.y);
// player = new Texture(Gdx.files.internal(fileName));
}
public void update(){
bounds.set(position.x, position.y, size.x, size.y);
}
public void draw(SpriteBatch batch){
batch.draw(player, position.x, position.y, size.x, size.y);
}
public Vector2 getPosition() {
return position;
}
public void setPosition(Vector2 position) {
this.position = position;
}
public Vector2 getSize() {
return size;
}
public void setSize(Vector2 size) {
this.size = size;
}
public Rectangle getBounds() {
return bounds;
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
}
Jiajun, create a Stage and have your Player extend Image. Then call stage.addActor(player) in show. In render call stage.act() and stage.draw(). Use the Image's constructor to pass your Texture into. Finally, in Player's constructor, call this:
addListener(new DragListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
return true;
}
#Override
public void touchDragged(InputEvent event, float x, float y, int pointer) {
moveBy(x - getWidth()/2, y - getHeight()/2);
}
});

Sprite flicker while moving libgdx

This code makes image Border flicker(Flash) while moving left,right,down or up. Why image border flash wile moving even if I use Screen class render() method delta value.
package com.me.mygdxgame;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector3;
public class MoveSpriteExample extends GdxTest implements InputProcessor {
Texture texture;
SpriteBatch batch;
OrthographicCamera camera;
Vector3 spritePosition = new Vector3();
Sprite sprite;
public void resize (int width, int height) {
}
public void create() {
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
batch = new SpriteBatch();
camera= new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.setToOrtho(false, w, h);
texture = new Texture(Gdx.files.internal("data/grasswall.png"));
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
sprite = new Sprite(texture);
sprite.setSize(32, 32);
spritePosition.y=100;
sprite.setPosition(spritePosition.x,spritePosition.x);
lastUpdateTime = System.nanoTime();
}
public void Update(float Delta)
{
if (Gdx.input.isKeyPressed(Keys.D)==true)
{
spritePosition.x += 150*(Delta / 1000000000.0);
}else if (Gdx.input.isKeyPressed( Keys.A)==true)
{
spritePosition.x -= 150*(Delta / 1000000000.0);
}
else if (Gdx.input.isKeyPressed( Keys.Z)==true)
{
spritePosition.y -= 150*(Delta / 1000000000.0);
}
else if (Gdx.input.isKeyPressed( Keys.W)==true)
{
spritePosition.y += 150*(Delta / 1000000000.0);
}
}
float lastUpdateTime;
float currentTime;
public void render() {
currentTime = System.nanoTime();
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
camera.update();
Update(currentTime - lastUpdateTime);
sprite.setPosition(spritePosition.x,spritePosition.y);
batch.setProjectionMatrix(camera.combined);
batch.begin();
sprite.draw(batch);
batch.end();
lastUpdateTime = currentTime;
}
}
pls provide code with sample
As i said in the last post of you, you should take a look at the Tutorial!
First of all you do not need to calculate the delta time yourself. I already postet how you get it in the last post for you.
Ill give you a super short example with a moving sprite without flickering.
At first here is the ApplicationListener. (not a GDXtest)
public class MainClass implements ApplicationListener {
private Screen currentScreen = null;
#Override
public void create() {
Texture.setEnforcePotImages(false);
this.currentScreen = new TestScreen();
}
#Override
public void dispose() {
this.currentScreen.dispose();
}
#Override
public void render() {
this.currentScreen.render(Gdx.graphics.getDeltaTime());
}
#Override
public void resize(int width, int height) {
this.currentScreen.resize(width, height);
}
#Override
public void pause() {
this.currentScreen.pause();
}
#Override
public void resume() {
this.currentScreen.resume();
;
}
}
It's preaty simple and as you can see it does call the render of the screen automatically with the delta time! this.currentScreen.render(Gdx.graphics.getDeltaTime()).
The next thing you need is a simple Screen. So something that does implement the Screeninterface. Id really recommand that you use a Scene2D setup with a stage. But here is an example without.
public class TestScreen implements Screen {
private Sprite mySprite;
private SpriteBatch batch = new SpriteBatch();
public TestScreen() {
this.mySprite = new Sprite(new Texture(
Gdx.files.internal("data/appicon.png")));
this.mySprite.setPosition(50f, 50f);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
this.update(delta);
this.batch.begin();
this.mySprite.draw(batch);
this.batch.end();
}
public void update(float delta) {
if (Gdx.input.isKeyPressed(Keys.D) == true) {
mySprite.setX(mySprite.getX() + 150 * delta);
} else if (Gdx.input.isKeyPressed(Keys.A) == true) {
mySprite.setX(mySprite.getX() - 150 * delta);
} else if (Gdx.input.isKeyPressed(Keys.Z) == true) {
mySprite.setY(mySprite.getY() - 150 * delta);
} else if (Gdx.input.isKeyPressed(Keys.W) == true) {
mySprite.setY(mySprite.getY() + 150 * delta);
}
}
#Override
public void resize(int width, int height) {
}
#Override
public void show() {
}
#Override
public void hide() {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
}
}
Regardas and do take a look at the tutorial. (Here is again the beginning of it)

Categories

Resources