Thread: Moribund - a 2D survival battle-royale where time is of the essence.

Page 1 of 6 123 ... LastLast
Results 1 to 10 of 55
  1. #1 Moribund - a 2D survival battle-royale where time is of the essence. 
    (Official) Thanksgiver

    Arham's Avatar
    Join Date
    Jan 2013
    Age
    23
    Posts
    3,415
    Thanks given
    7,254
    Thanks received
    1,938
    Rep Power
    3905
    Moribund is a 2D battle-royale where time is of the essence. At the beginning of the game, one chooses their respective type of player. This will determine certain abilities the players have, and the amount of time they start with. What differs Moribund to a multitude of battle-royales out there is the concept of time. In a typical battle-royale, the motivation to make players not hide in a game is a storm that is slowly zooming to the center of the map. If a player is caught outside of the safe zone in the center, the storm slowly damages the player to their demise. In Moribund, however, the concept of time is a new driving factor. While a storm is debatable, the idea of time being of the essence is what is aimed to be the catalyst for what drives a player to not simply hide their way to victory. When a player is spawned in the game, they start with a timer that is counting down to their demise. How do you survive? Kill another person and absorb some of their time! Another unique aspect of Moribund is that it is medieval, meaning that items such as darts, crossbows, short bows, longbows, and javelins are used instead of the typical idea of “guns” (I totally didn't use RuneScape's ranged weapons). What these more simple weapons create is a unique opportunity: the construction of weapons. In Moribund, one does not find weapons on the ground. Rather, they are spawned with a default, weak weapon in accordance with their type of player, and have the ability to construct new weapons using materials found around the map. The goal, like any battle-royale, is to the be the last one standing!

    I started development of this battle-royale around November 23rd, and only have gotten an opportunity to work on it in long breaks (Thanksgiving break, Winter break, etc.). The reason I am making it is because I am enrolled in a Software Engineering competition: BPA. If you do visit the site, you'll see that the project is due January 25th! That's 22 days since this thread's creation!

    Client: https://github.com/Moribund-MB/Moribund-Client
    Server: https://github.com/Moribund-MB/Moribund-Server
    Website (documentation): https://moribund-mb.github.io/

    The source code is almost written entirely by myself. So, I would appreciate criticism of the code if you could spare your time to give some.

    Spoiler for Progress:

    November 25th - Start on project, which is a start on the multi-player rendering too
    Attached image

    December 8th - Rotation and movement, including multi-player rendering of said rotation
    Spoiler for Funny Bug GIF:

    Attached image

    Spoiler for Bug:

    Attached image

    Attached image

    January 1st - Game state packet sent every 100 MS, camera movement with respect to boundaries, movement with respect to boundaries, projectiles
    Attached image
    Attached image
    Attached image

    January 3rd - New character image (subject to change), new map (still not official map), unknowingly had multi-player rendering of projectiles due to how our code is structured
    Attached image
    Attached image
    Attached image

    January 4th - New player sprite
    Attached image

    January 5th - Start on logging in, including SQL!
    Attached image

    January 7th - Room system, projectile collision (using basic rectangles), UI work, ground items, and item containers (inventory, equipment, etc.)
    Attached image
    Attached image
    Attached image
    Attached image
    Attached image

    January 20th - New map, more precise collision, start on combat (HP tracking), new login screen, start on new UI
    Attached image
    Attached image

    January 21st - Health bars, item on item interaction, item equipping, appearance updating
    Attached image
    Attached image

    January 22nd - Animations, with handshakes between the server and client and such
    Attached image

    January 23rd - Timer mechanic (just the functionality of it and transfering text to client, still need visuals), fixed multiple bugs, polygons for darts and spear projectiles, some server-sided handling of combat, prevented multilogging, generation of crash reports on application failure and emailing them, shut game client-sided when server crashes
    Attached image
    Attached image

    January 24th - Visual aspects for timers (both lobby and death), the entire lobby system, death handling, time absorbing, item unequipping, new menu UI, and item switching when equipping a new item
    Attached image
    Attached image
    Attached image
    Attached image
    Attached image
    Note: Yes, the player still stayed rendered, but the point of the GIF is to show the time absorption.

    January 25th - PROJECT SUBMITTED! Tutorial, escape key to escape games, text upon winning a game, dropping items, and probably more.
    Attached image
    Attached image
    Attached image
    Attached image

    February 3rd - Player following the mouse, movement independent of your angle, camera zoomed out.
    Attached image
    Attached image
    Attached image


    Moribund uses multiple third-party dependencies. These dependencies include LibGDX, KryoNet, fastutils, and Project Lombok. We also have plans to use jOOQ. LibGDX is a game engine written in Java using LWJGL, which is a wrapper for the OpenGL graphics engine. KryoNet is a networking library that utilizes Java’s serialization library to send serialized Kryo-nized data over networking. This allows for entire classes to be sent over the connection and have custom serializers be made for them. Fastutils is a library that has common data structures in Java made in a more efficient way as the vanilla Java data structures are comparatively more expensive in memory due to the nature of Java’s generics system (shoutout to Kris). Lombok is a library that allows auto-generation of Java code for common Java utilities, such as getters and setters and builder classes. Our plan to use jOOQ is to allow for PostgreSQL to be used for account creation and saving.

    Spoiler for More nerdy explanations (not up to date, written December 23rd):

    Our code is built to follow the SOLID principles: the Single-Responsibility Principle, the Open-Closed Principle, Liskov’s Substitution Principle, the Interface Segregation Principle, and Dependency Inversion. Although our code at its current state has over 6,000 lines of code, the fact we follow the Single-Responsibility Principle allows for this code to be evenly distributed across a multitude of classes rather than having one “super-class” that has thousands of lines with the game logic in it. The Open-Closed Principle allows us to have easily testable entity classes as one can simply use encapsulation to test a completely different class. Liskov’s Substitution Principle allows for attributes to be clearly defined for entities, such as if it is walkable or what not. This, quite similarly, goes hand-in-hand with the interface segregation principle which more or less says to divide interfaces into singly-responsible interfaces rather than handling multiple different tasks. Dependency Inversion is perhaps the most heavily used principle in our project. This usage of Dependency Inversion is what solidies all the other principles. For example, to construct the client, there is a MoribundClientFactory that creates all the dependencies the MoribundClient uses. This strengthens and secures the client immensely, while still keeping everything at ease, as the client factory can be made only in the package-private, and the client can only be made if all the dependencies are provided. An example in code is the following:
    Code:
    import lombok.val;
    
    class MoribundClientFactory {
        MoribundClient createMoribundClient() {
            val players = createPlayersMap();
            val networkBootstrapper = createNetworkBootstrapper();
            val packetDispatcher = createPacketDispatcher(networkBootstrapper);
            val titleScreenFactory = createTitleScreenFactory();
            return new MoribundClient(players, networkBootstrapper, packetDispatcher, titleScreenFactory);
        }
        private ScreenFactory createTitleScreenFactory() {
            return new TitleScreenFactory();
        }
        private PacketDispatcher createPacketDispatcher(NetworkBootstrapper networkBootstrapper) {
            return networkBootstrapper.createPacketDispatcher();
        }
        private NetworkBootstrapper createNetworkBootstrapper() {
            return new NetworkBootstrapper();
        }
        private AbstractInt2ObjectMap<PlayableCharacter> createPlayersMap() {
            return new Int2ObjectOpenHashMap<>();
        }
    }
    
    public class MoribundClient extends Game {
        ...
        @Getter
        private final AbstractInt2ObjectMap<PlayableCharacter> players;
        private final ScreenFactory titleScreenFactory;
        private final NetworkBootstrapper networkBootstrapper;
        private final PacketDispatcher packetDispatcher;
    
        MoribundClient(AbstractInt2ObjectMap<PlayableCharacter> players,
                               NetworkBootstrapper networkBootstrapper,
                               PacketDispatcher packetDispatcher,
                               ScreenFactory titleScreenFactory) {
            this.players = players;
            this.networkBootstrapper = networkBootstrapper;
            this.packetDispatcher = packetDispatcher;
            this.titleScreenFactory = titleScreenFactory;
        }
    
        ...
    }
    In this code, the MoribundClientFactory creates the dependencies that the client needs in order to function. To further explain, through the use of Dependency Inversion, the client factory utilizes the Single-Responsibility Principle as it is solely responsible for creating a MoribundClient and its dependencies. Another example of how the the Dependency Inversion Principle strengthens our usage of all the other principles is in our ScreenFactory interface.
    Code:
    public interface ScreenFactory {
        Screen createScreen();
    }
    public class TitleScreenFactory implements ScreenFactory {
        @Override
        public Screen createScreen() {
            val musicPlayer = createMusicPlayer();
            val gameScreenFactory = createGameScreenFactory();
            val settingsScreenFactory = createSettingsScreenFactory();
            return new TitleScreen(musicPlayer, gameScreenFactory, settingsScreenFactory);
        }
        private ScreenFactory createGameScreenFactory() {
            return new GameScreenFactory();
        }
        private ScreenFactory createSettingsScreenFactory() {
            return new SettingsScreenFactory();
        }
        private MusicPlayer createMusicPlayer() {
            return new MusicPlayer();
        }
    }
    The fact that we were wishing to use Dependency Inversion motivated us to use the Open-Closed Principle. By means of using the Open-Closed Principle, we now have flexible ways to change the screen factories for a respective class with minimal effort. For example, if we wished to start the game at the GameScreen instead of the TitleScreen, all we would have to do is go to the MoribundClientFactory and make a method that creates a GameScreen using the GameScreenFactory which implements ScreenFactory. Then, we would simply pass in the newly created GameScreen dependency to the constructor of the MorbundClient and the game would start with the GameScreen with no errors at all.

    Another heavily used practice is the Singleton pattern. The classes that utilize the Singleton pattern are the MusicContainer, SpriteContainer, and MoribundClient classes. The music and sprite container classes utilize the pattern to allow a cache of the music and sprites loaded from the start of the program with attachments to their third-party class implementations in LibGDX. A visualization of this would be the following code:
    Code:
       private final Object2ObjectOpenHashMap<MusicFile, Music> musicForFile;
    In this case, the MusicFile is coupled with the Music class itself, which allows for us to use Music in the LibGDX library in relation to the MusicFile itself. Using a Singleton allows us to not only have lazy initialization of the music files, but also lets us have a cache of Music in a data structure of O(1) time complexity. The same is true for the SpriteContainer class.

    The MoribundClient is a unique case in this respect of the Singleton pattern. The entire use of Singleton pattern in the class is due to the following variables:
    Code:
       @Getter @Setter
       private PlayableCharacter player;
       @Getter
       private final PacketDispatcher packetDispatcher;
    The accessibility of the player variable is integral for the game as this variable is the exact PlayableCharacter being controlled by the respective client. The packetDispatcher is also needed in order to allow packets to be sent from places other than the Listeners. This topic will be discussed next.

    Networking is done with the NetworkBootstrapper, Packets, the PacketDispatcher, and Listeners. The NetworkBootstrapper is responsible for giving the initial instructions to start the networking process and listeners. Important configurations can be found within this class.
    Code:
        private static final int INITIAL_TIMEOUT = 3000;
        private static final String IP_ADDRESS = "localhost";
        private static final int PORT = 43594;
    Moreover, all the Packets (and the serializable classes they use) are registered in this class.
    Code:
       private void registerPackets(Kryo kryo) {
            kryo.register(DrawNewPlayerPacket.class);
            kryo.register(LoginPacket.class);
            kryo.register(LoginRequestPacket.class);
            kryo.register(ArrayList.class, new JavaSerializer());
            kryo.register(Pair.class, new JavaSerializer());
            kryo.register(Integer.class, new JavaSerializer());
            kryo.register(KeyPressedPacket.class);
            kryo.register(KeyPressedResponsePacket.class);
            kryo.register(KeyUnpressedPacket.class);
            kryo.register(KeyUnpressedResponsePacket.class);
            kryo.register(LocationPacket.class);
            kryo.register(RotationPacket.class);
        }
    Note that we have plans to possibly use the classpath-scanner library to automatically scan for all classes that implement the Packet interface and auto-register them. However, this is a slight possibility, as this would disable the flexibility of custom serialization possibly.

    The Packet interface and PacketDispatcher go hand in hand. The PacketDispatcher class is responsible for the sending of packets from the client to the server. It provides a restrictive access to the KryoNet Client to the public classes. At the current time, the PacketDispatcher is used to send UDP packets restrictively. This constraint is enforced as the methods require a Packet class in the parameter rather than the Object class that default KryoNet gives. This allows for less vulnerability of unneeded data being sent to the server.
    Code:
    public class PacketDispatcher {
        private final Client client;
    
        PacketDispatcher(Client client) {
            this.client = client;
        }
    
        public void sendTCP(Packet packet) {
            client.sendTCP(packet);
        }
    }
    The Listeners are what make the networking work. These classes listen to packets that are related to separate components of the game, which enforces the Single-Responsibility Principle. These packets are resolved by instanceof comparison and class casting.
    Code:
       @Override
       public void received(Connection connection, Object object) {
            if (object instanceof KeyPressedResponsePacket) {
                val keyPressedResponsePacket = (KeyPressedResponsePacket) object;
                val player = MoribundClient.getInstance().getPlayers().get(keyPressedResponsePacket.getPlayerId());
                player.keyPressed(keyPressedResponsePacket.getKeyPressed());
            } else if (object instanceof KeyUnpressedResponsePacket) {
                val keyUnpressedResponsePacket = (KeyUnpressedResponsePacket) object;
                val player = MoribundClient.getInstance().getPlayers().get(keyUnpressedResponsePacket.getPlayerId());
                player.keyUnpressed(keyUnpressedResponsePacket.getKeyUnpressed());
            }
        }
    The current flow of the program in regards to networking is the following:

    > Player starts the game, NetworkBootstrapper is connected.
    > Once player clicks “Find Match,” the LoginRequestPacket is sent.
    > The server receives the LoginRequestPacket and creates a player using the connection ID as the player ID. The player is then given a default x and y location in the game, added to the list of players in the server, and two packets are sent: the LoginPacket and the DrawNewPlayerPacket.
    > The LoginPacket is sent to the client of the player that has just been made. The data that is sent to the client is the list of all the player IDs, the player locations, player rotations, and the player ID of the newly created player.
    > The DrawNewPlayerPacket is sent to all of the other players. This data that is sent is solely information about the newly created player, such as the player ID and the location of the player.
    > A player has now logged in and the client and server have been synchronized with the same data.
    > When a player clicks a button, a keybind of the respective PlayableCharacter is invoked. This provokes the Runnable interface to be run. Once the player has released the key, the keybind is invoked again, but this time of its key release component. This key is sent synchronously to the server.

    Code:
    public interface PlayableCharacter {
        ...
        void bindKeys();
        AbstractInt2ObjectMap<PlayerAction> getKeyBinds();
        Set<Flag> getFlags();
        void keyPressed(int keyPressed);
        void keyUnpressed(int keyUnpressed);
    }
    
    public interface PlayerAction {
        void keyPressed();
        void keyUnpressed();
    }
    ...
           keyBinds.put(Input.Keys.UP, new PlayerAction() {
                @Override
                public void keyPressed() {
                    flag(Flag.MOVE_UP);
                }
    
                @Override
                public void keyUnpressed() {
                    unflag(Flag.MOVE_UP);
                    sendTilePacket();
                }
            });
    ...
    > Every 100 milliseconds, a game state packet is sent by the server to the client and the client blindly obeys whatever the server says. This means that the server follows an asynchronous pattern, which allows for packets to be enacted appropriately.


    Hopefully, this project shall be finished on January 25th! After that, I might just invite some people to play around or something! I don't know though.
    Attached image
    Attached image
    Quote Originally Posted by MrClassic View Post
    Arham is the official thanker!
    List of my work here!
    Reply With Quote  
     


  2. #2  
    nice


    Join Date
    Jul 2014
    Posts
    740
    Thanks given
    382
    Thanks received
    562
    Rep Power
    4239
    Was this copied from Surviv.io ?
    http://prntscr.com/m2pg1h
    Nice game either way
    Reply With Quote  
     

  3. #3  
    Donator


    Join Date
    Jul 2017
    Posts
    178
    Thanks given
    196
    Thanks received
    55
    Rep Power
    102
    Dope
    Reply With Quote  
     

  4. Thankful user:


  5. #4  
    (Official) Thanksgiver

    Arham's Avatar
    Join Date
    Jan 2013
    Age
    23
    Posts
    3,415
    Thanks given
    7,254
    Thanks received
    1,938
    Rep Power
    3905
    Quote Originally Posted by Thesuic12 View Post
    Was this copied from Surviv.io ?
    http://prntscr.com/m2pg1h
    Nice game either way
    Yeah, I copied the map for it as a temporary map. As I said though, it's just a temporary one.
    Attached image
    Attached image
    Quote Originally Posted by MrClassic View Post
    Arham is the official thanker!
    List of my work here!
    Reply With Quote  
     

  6. Thankful user:


  7. #5  
    Donator

    Jason's Avatar
    Join Date
    Aug 2009
    Posts
    6,092
    Thanks given
    2,402
    Thanks received
    2,823
    Rep Power
    4550
    Looking forward to seeing the progress this makes. That being said I think the description is a bit verbose and hard to read, might want to simplify it a bit for retards such as myself. LibGDX is a nice library, been using it for over 2 years now, unfortunately Nathan Sweet is just maintaining it and not advancing the project. Kryo is nice at first but you notice after a while that it sucks for backwards compatibility and is restrictive.
    Reply With Quote  
     

  8. Thankful user:


  9. #6  
    (Official) Thanksgiver

    Arham's Avatar
    Join Date
    Jan 2013
    Age
    23
    Posts
    3,415
    Thanks given
    7,254
    Thanks received
    1,938
    Rep Power
    3905
    Quote Originally Posted by Jason View Post
    Looking forward to seeing the progress this makes. That being said I think the description is a bit verbose and hard to read, might want to simplify it a bit for retards such as myself.
    Thank you! Do you mean the description for the game itself?

    Quote Originally Posted by Jason View Post
    LibGDX is a nice library, been using it for over 2 years now, unfortunately Nathan Sweet is just maintaining it and not advancing the project.
    LibGDX is alright. I don't like how static it is and the abstract classes are sort of annoying considering how some of them have a multitude of methods that aren't implemented that also require you to go to the project source code to know what to implement and what not.

    Quote Originally Posted by Jason View Post
    Kryo is nice at first but you notice after a while that it sucks for backwards compatibility and is restrictive.
    Oh definitely. I moreso went with KryoNet due to the fact I've never done networking besides KryoNet, the competition is just a buncha highschool kids, and it was faster for me to do (since I've used it before) considering the close deadline I have.
    Attached image
    Attached image
    Quote Originally Posted by MrClassic View Post
    Arham is the official thanker!
    List of my work here!
    Reply With Quote  
     

  10. Thankful user:


  11. #7  
    ᐯᗴᑎᗝᗰ丨丅ᗴ
    AryJaey's Avatar
    Join Date
    Apr 2016
    Posts
    253
    Thanks given
    91
    Thanks received
    112
    Rep Power
    667
    Nice to finally see the progress on what you got so far! Give that graphic partner of yours a bump from my part
    Goodluck again, could be real fun and looking forward to actually play it!


    Attached image
    Reply With Quote  
     

  12. Thankful user:


  13. #8  
    OS-Anguish

    Euphoric's Avatar
    Join Date
    Nov 2018
    Posts
    73
    Thanks given
    24
    Thanks received
    32
    Rep Power
    87
    nice man
    Attached image
    Reply With Quote  
     

  14. Thankful user:


  15. #9  
    (Official) Thanksgiver

    Arham's Avatar
    Join Date
    Jan 2013
    Age
    23
    Posts
    3,415
    Thanks given
    7,254
    Thanks received
    1,938
    Rep Power
    3905
    Attached image

    This is starting to look more and more like surviv.io lol, dw soon the map will change and it'll be lit
    Attached image
    Attached image
    Quote Originally Posted by MrClassic View Post
    Arham is the official thanker!
    List of my work here!
    Reply With Quote  
     

  16. #10  
    (Official) Thanksgiver

    Arham's Avatar
    Join Date
    Jan 2013
    Age
    23
    Posts
    3,415
    Thanks given
    7,254
    Thanks received
    1,938
    Rep Power
    3905
    Started using jOOQ and started logging in with account creation (similar to RSPS). Passwords are also encrypted!

    Attached image
    Attached image
    Attached image
    Quote Originally Posted by MrClassic View Post
    Arham is the official thanker!
    List of my work here!
    Reply With Quote  
     

Page 1 of 6 123 ... LastLast

Thread Information
Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)


User Tag List

Similar Threads

  1. Dead-Zone "Where there is no survival"
    By vovik ukr in forum Advertise
    Replies: 210
    Last Post: 09-04-2012, 04:27 PM
  2. Battle Royale
    By Benji in forum Projects
    Replies: 71
    Last Post: 09-26-2011, 04:25 AM
  3. Artemis - A 2D Survival and exploration game.
    By tommo in forum Application Development
    Replies: 6
    Last Post: 06-17-2011, 05:41 PM
  4. battle-royale.no-ip.biz / hamachi : 5.123.145.113
    By iniquity in forum RS2 Server
    Replies: 1
    Last Post: 01-18-2008, 08:09 PM
Posting Permissions
  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •