Skip to content

(Outdated) Legacy: Getting Started Guide: Building a simple game

Vrekt edited this page May 25, 2022 · 1 revision

Looking to get a grasp on how things work? You came to the right place!

We will be building a simple game with player movement, forces and multiplayer.

Creating a simple server

Ensure you have the Server module included in your project. Creating a server is super easy and everything is already done for us, we just have to initialize it.

final LunarNettyServer netty = new LunarNettyServer("localhost", 6969);
netty.bind().join();

final ImplLunarServer server = new ImplLunarServer();
server.start();

That's it! Now lets move onto the client...

The Game

In your LibGDX method create() you should initialize lunar and create a connection to the server.

// create a new lunar instance.
final Lunar lunar = new Lunar();
lunar.setGdxInitialized(true);

// connect to remote server.
server = new LunarClientServer(lunar, "localhost", 6969);
server.connect().join();

// get our connection
final PlayerConnection connection = (PlayerConnection) server.getConnection();

Telling lunar GDX is initialized will allow it to log using Gdx.app.log. Next, lets establish some basic properties about how players and our world should be drawn. For this example, my world is at a scale of 1 / 16 and my players are 16x18.

// default world and camera scaling.
final float scaling = 1 / 16.0f;
// Initialize basic properties about how a player should be drawn.
final PlayerProperties basic = new PlayerProperties(scaling, 16f, 18f);
lunar.setPlayerProperties(basic);

Now, we can create our Box2d world and initialize our local player.

// Create our Box2D world.
final World world = new World(Vector2.Zero, true);
world.setContactListener(new PlayerCollisionListener());

// Create our local player.
player = new LunarPlayer(scaling, basic.width, basic.height, Rotation.FACING_UP);
player.setConnection(connection);
connection.setPlayer(player);

// tell the server our username
connection.sendSetUsername("SomeCoolPlayer");

// tell the server we want to join a world.
connection.send(new CPacketJoinWorld(connection.alloc(), "LunarWorld"));

Adding the default collision listener will allow us to remove player collisions, since we can just ignore those in our case. We give our player a default starting rotation of FACING_UP and provide the width, height and scaling. Finally, we can tell the player the connection to use!

Then, we can setup the renderer our player is going to use. You should use the example character atlas (not 2) in this github under /core/assets/.

// Supply this player with our textures.
atlas = new TextureAtlas("character.atlas");
player.initializePlayerRendererAndLoad(atlas, true);

By default, Lunar follows a pre-set naming convention when it comes to player animations. To keep it simple in this tutorial, we just abide by those, although there is of-course a way to not use them and have everything work fine. Check out the wiki to see how to do that.

Now, for each player that is joining were gonna give them the same textures. We can do this by adding a join world listener.

// Supply all other players who join our world with those textures as-well.
connection.setJoinWorldListener(networkPlayer -> networkPlayer.initializePlayerRendererAndLoad(atlas, true));

The renderer is all good to go so lets move on!

Worlds are an essential part of Lunar. They keep track of all players and sync certain aspects. Let's create one.

// Create a networked world for others to join us.
// We tell the world to handle physics updates and local player updates for us.
lunarWorld = new LunarWorldAdapter(player, world, scaling, true, true, true, true);
// Spawn our player in the world.
player.spawnEntityInWorld(lunarWorld, 2.0f, 2.0f);
// set a random name for this player
// this MUST be set after sending the join world packet.
player.setName("SomeCoolPlayer");

In this tutorial, we have the world handle updating the Box2d world and local player for us. We also spawn the player at a starting position of 2.0, 2.0.

Finally, we can setup the graphics and camera we are going to use within LibGDX and tell the server we are ready to go!

// Initialize our graphics for drawing.
camera = new OrthographicCamera();
camera.setToOrtho(false, Gdx.graphics.getWidth() / (scaling / 2.0f), Gdx.graphics.getHeight() / (scaling / 2.0f));
viewport = new ExtendViewport(Gdx.graphics.getWidth() / scaling, Gdx.graphics.getHeight() / scaling);

// Set the initial camera position
camera.position.set(2.0f, 2.0f, 0.0f);
camera.update();

batch = new SpriteBatch();

// let the server know we have loaded everything.
connection.send(new CPacketWorldLoaded(connection.alloc()));

That's it! Lets move on to rendering and we will be all good to go to play!

Rendering The Game

We can start rendering by setting the camera position to the players, so they are always in the center.

camera.position.set(player.getInterpolated().x, player.getInterpolated().y, 0f);
camera.update();

Using the players interpolated position will give us a smooth example. Next, lets clear the screen.

// clear screen.
Gdx.gl.glClearColor(69 / 255f, 8f / 255f, 163f / 255, 0.5f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

Now, before we draw lets update the world.

 // update the world.
final float delta = Gdx.graphics.getDeltaTime();
lunarWorld.update(delta);

Finally, we can draw the world and our player!

 // begin batch
batch.setProjectionMatrix(camera.projection);
batch.begin();

// render our world.
lunarWorld.renderWorld(batch, delta);
// render our player
player.render(batch, delta);

// done :)
batch.end();

Finishing up

Lets add the finishing touches. Ensure you have the resize method or you wont be able to see your world.

    @Override
    public void resize(int width, int height) {
        viewport.update(width, height, false);
        camera.setToOrtho(false, width / 16f / 2f, height / 16f / 2f);
    }

    @Override
    public void dispose() {
        player.dispose();
        lunarWorld.dispose();
        atlas.dispose();
        batch.dispose();
        server.dispose();
    }

The Full Class

public final class BasicExampleMain extends Game {

    private LunarClientServer server;

    private LunarWorldAdapter lunarWorld;
    private TextureAtlas atlas;

    private LunarPlayer player;

    private OrthographicCamera camera;
    private ExtendViewport viewport;
    private SpriteBatch batch;

    public BasicExampleMain() {
    }

    @Override
    public void create() {
        // create a new lunar instance.
        final Lunar lunar = new Lunar();
        lunar.setGdxInitialized(true);

        // connect to remote server.
        server = new LunarClientServer(lunar, "localhost", 6969);
        server.connect().join();

        // get our connection
        final PlayerConnection connection = (PlayerConnection) server.getConnection();

        // default world and camera scaling.
        final float scaling = 1 / 16.0f;

        // Initialize basic properties about how a player should be drawn.
        final PlayerProperties basic = new PlayerProperties(scaling, 16f, 18f);
        lunar.setPlayerProperties(basic);

        // Create our Box2D world.
        final World world = new World(Vector2.Zero, true);
        world.setContactListener(new PlayerCollisionListener());

        // Create our local player.
        player = new LunarPlayer(scaling, basic.width, basic.height, Rotation.FACING_UP);
        player.setConnection(connection);
        connection.setPlayer(player);

        connection.sendSetUsername("SomeCoolPlayer");
        // tell the server we want to join a world.
        connection.send(new CPacketJoinWorld(connection.alloc(), "LunarWorld"));

        // Supply this player with our textures.
        atlas = new TextureAtlas("character.atlas");
        player.initializePlayerRendererAndLoad(atlas, true);

        // Supply all other players who join our world with those textures as-well.
        connection.setJoinWorldListener(networkPlayer -> networkPlayer.initializePlayerRendererAndLoad(atlas, true));

        // Create a networked world for others to join us.
        // We tell the world to handle physics updates and local player updates for us.
        lunarWorld = new LunarWorldAdapter(player, world, scaling, true, true, true, true);
        // Spawn our player in the world.
        player.spawnEntityInWorld(lunarWorld, 2.0f, 2.0f);
        // set a random name for this player
        // this MUST be set after sending the join world packet.
        player.setName("SomeCoolPlayer");
        // Initialize our graphics for drawing.
        camera = new OrthographicCamera();
        camera.setToOrtho(false, Gdx.graphics.getWidth() / (scaling / 2.0f), Gdx.graphics.getHeight() / (scaling / 2.0f));
        viewport = new ExtendViewport(Gdx.graphics.getWidth() / scaling, Gdx.graphics.getHeight() / scaling);

        // Set the initial camera position
        camera.position.set(2.0f, 2.0f, 0.0f);
        camera.update();

        batch = new SpriteBatch();

        // let the server know we have loaded everything.
        connection.send(new CPacketWorldLoaded(connection.alloc()));
    }

    @Override
    public void render() {
        // update our camera
        camera.position.set(player.getInterpolated().x, player.getInterpolated().y, 0f);
        camera.update();
        update();

        // clear screen.
        Gdx.gl.glClearColor(69 / 255f, 8f / 255f, 163f / 255, 0.5f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // update the world.
        final float delta = Gdx.graphics.getDeltaTime();
        lunarWorld.update(delta);

        // begin batch
        batch.setProjectionMatrix(camera.projection);
        batch.begin();

        // render our world.
        lunarWorld.renderWorld(batch, delta);
        // render our player
        player.render(batch, delta);

        // done :)
        batch.end();
    }

    private void update() {
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            System.err.println("APPLYING FORCE");
            // test apply forces over the network.
            final Vector2 point = player.getBody().getWorldPoint(new Vector2(5.0f, -5));
            final float fx = player.getBody().getMass() * (player.getX() * 150);
            final float fy = player.getBody().getMass() * (player.getY() * 150);
            this.player.getWorldIn().applyForceToPlayerNetwork(player.getConnection(), fx, fy, point.x, point.y, true);
        }
    }

    @Override
    public void resize(int width, int height) {
        viewport.update(width, height, false);
        camera.setToOrtho(false, width / 16f / 2f, height / 16f / 2f);
    }

    @Override
    public void dispose() {
        player.dispose();
        lunarWorld.dispose();
        atlas.dispose();
        batch.dispose();
        server.dispose();
    }
}