Crafting System Code Implementation Sprint One - UQdeco2800/2022-studio-2 GitHub Wiki

Introduction

The crafting system code implementation has been divided into two main parts at this stage, the first part is the "crafting system & logic", and the second is the "crafting display", specifically how visual assets will be connected to the gameplay while also processing player's inputs.

For sprint one, the programming squad decided to make the foundation, that means the underlying logic of each classes was implemented. For exmple, the main purpose is to successfully craft a basic sword. As for the “display animation”, there will be no aimation at this stage, this means that the "display animation" class will only be dealing with using text/numbers to indicate player's interaction. For example, if player crafted a sword, instead of displaying crafting animation, there will only be a sword image with a number 1 on it to indicate one sword has been successfully crafted.

Some fundamental unit testing will also be desigend to test for some base cases.

Crafting Logic

Implemented by @rkoll55

public static List<Object> canBuild(List<Materials> inventoryContents){
    List<Object >buildables = new ArrayList<Object>();
    List<Object> weapons = new ArrayList<>();
    weapons.addAll(possibleWeapons);

    for (int i = 0 ; i < weapons.size(); i++){

        if (weapons.get(i) instanceof Buildable){
            Map<Materials,Integer> materialValues = ((Buildable) weapons.get(i)).getRequiredMaterials();
            for (Map.Entry<Materials, Integer> entry : materialValues.entrySet()){

                if (!inventoryContents.contains(entry.getKey())){
                }

                else if (Collections.frequency(inventoryContents, entry.getKey()) < entry.getValue()){
                }

                else {
                    buildables.add(weapons.get(i));
                }
            }
        }
    }
    if (inventoryContents.contains(Materials.Wood) && inventoryContents.contains(Materials.Steel)){
        buildables.add("Sword");
    }
    return buildables;
}

The main method implemented in this class is the "canBuild" method, it determines what items can be built based on items present in users' inventory. It checks if

  • object first implements buildable interface
  • then checks if user has required materials to build it.

It is assumend that for the first sprint, player will always have enough materials in their inventory.

Crafting System

Implemented by @rkoll55

public void CraftingSystem() {

     builtItems = new ArrayList<String>();
     //Set Possible Builds by finding all weapons that implement Buildable
    HashSet<Object> possibleWeapons = new HashSet<Object>();
    possibleWeapons.add("Sword");
    CraftingLogic.setPossibleWeapons(possibleWeapons);

     //List<Materials> inventoryContents = getInventoryContents();
    inventoryContents = new ArrayList<>(); inventoryContents.add(Materials.Wood); inventoryContents.add(Materials.Steel); inventoryContents.add(Materials.Steel);
    CraftingLogic.setPossibleBuilds(CraftingLogic.canBuild(inventoryContents));

    CraftingDisplay UI = new CraftingDisplay();

    Thread background = new Thread(this);
    Thread display = new Thread(UI);

    background.setDaemon(true);
    background.start();
    display.start();

}

The main method created in this class is the "CraftingSystem", it is a constructor which creates an instance of the crafting system class. Specifically, the method creates a set of the users inventory and

  • Determines what items the users can build with it.
  • Class then calls an instance of the display to be made.
  • And finally creates a daemon that checks the users inventory.

The purpose of having this method is to outline the underlying logic of the crafting system, it is the core mechanism of how the crafting system will recoganize the items. More importantly, it concatenates the crafting display and the crafting system.

Interface - Buildable

Implemented by @rkoll55

image

This is a Public Interface that indicates what items are buildable and not in a game. To make an item buildable, implement the interface and override its base method.

The purpose of having this interface is to benefit the "combat items", the intention is to let all the buildable weapons implements buildable for the sake of convenience and code simplicity.

Enumeartion - Materials

Implemented by @rkoll55

image

As mentioned in "Crafting Visual Assets Design" and introduction, the plan is to successfully craft a basic sword for sprint one, and the materials used to craft a sword will just be wood and steel. Therefore, the purpose of having this material enum class is to store the materials for later use. The convenience and benefits of having this enum will be revealed in later sprints. (Since by then there will be more and more materials for crafting recipes.)

Crafting Display section

Implemented by @lyphng The main tasks for crafting display are:

  1. Display the crafting table on gameplay map.
  2. The player can interact with the crafting table using keys.
  3. The player is able to open and close the crafting UI.
  4. Craft a basic sword with wood and steel.

In order to achieve the above tasks, I modified the following java classes:

ForestGameArea, GameAreaDisplay, KeyboardPlayerInputComponent, OpenCraftingComponent, PlayerFactory, ObstacleFactory and ServiceLocator

The crafting table first needs to be created using the function in 'ObstacleFactory':

public static Entity createCraftingTable() {
    Entity craftingTable =
            new Entity()
                    .addComponent(new TextureRenderComponent("images/Crafting-assets-sprint1" +
                            "/crafting table/craftingTable.png"))
                    .addComponent(new PhysicsComponent())
                    .addComponent(new ColliderComponent().setLayer(PhysicsLayer.OBSTACLE));

    craftingTable.getComponent(PhysicsComponent.class).setBodyType(BodyType.StaticBody);
    craftingTable.getComponent(TextureRenderComponent.class).scaleEntity();
    craftingTable.scaleHeight(1.5f);
    PhysicsUtils.setScaledCollider(craftingTable, 0.8f, 0.7f);
    return craftingTable;
  }

To spawn the crafting table, the ForestGameArea needs to be modified, the image of the crafting table needs to be imported to the class first by modifying:

    private static final String[] forestTextures = {
        "images/Crafting-assets-sprint1/crafting table/craftingTable.png",
    };

Then using this method to display the crafting table at a random position:

    public void spawnCraftingTable() {
        GridPoint2 minPos = new GridPoint2(2, 2);
        GridPoint2 maxPos = terrain.getMapBounds(0).sub(4, 4);

        GridPoint2 randomPos = RandomUtils.random(minPos, maxPos);
        craftingTablePos = randomPos;
        Entity craftingTable = ObstacleFactory.createCraftingTable();
        spawnEntityAt(craftingTable, randomPos, true, false);
   }

The position of the crafting table is stored in the craftingTablePos variable and returned using the function getCraftingTablePos():

  public static GridPoint2 getCraftingTablePos() {
    return craftingTablePos;
  }

This is then called in the 'OpenCraftingComponent' class so that the position of the player and the crafting table can be compared:

    public void create() {

        logger = LoggerFactory.getLogger(OpenCraftingComponent.class);
        this.craftingTableXCoord = (float)ForestGameArea.getCraftingTablePos().x;
        this.craftingTableYCoord = (float)ForestGameArea.getCraftingTablePos().y;

        entity.getEvents().addListener("can_open", this::openCrafting);
        entity.getEvents().addListener("can_close", this::closeCrafting);

    }

    private void openCrafting() {
        if (entity.getCenterPosition().dst(craftingTableXCoord, craftingTableYCoord) < 10 && isOpen == false) {
            ServiceLocator.getCraftArea().openCraftingMenu();
            isOpen = true;
        }

    }

This component is also added to the Playerfactory class so it can read the player's current position. The "can_open" and "can_close" events are triggered in 'KeyboardPlayerInputComponent' when the player presses Q and L respectively:

  @Override
  public boolean keyDown(int keycode) {
    switch (keycode) {
      case Keys.Q:
        entity.getEvents().trigger("can_open");
        return true;
      case Keys.L:
        System.out.println("9");
        entity.getEvents().trigger("can_close");
        return true;
      case Keys.E:
        entity.getEvents().trigger("skill");
        return true;
      case Keys.SHIFT_LEFT:
        entity.getEvents().trigger("dash");
        return true;
      case Keys.I:
        entity.getEvents().trigger("toggleInventory");
        return true;
      default:
        return false;
    }

The getCraftArea() function is defined in 'ServiceLocator' and gets the current state of the game to allow the screen to appear after the game is initialised:

  public static void registerCraftArea(GameAreaDisplay area){
    craftArea = area;
  }

  public static GameAreaDisplay getCraftArea() {
    return craftArea;
  }

The screen is loaded in GameAreaDisplay. The items wood and steel will appear on the left in the inventory display area when the menu is open. When the user clicks on each item, it will show up in the crafting slots and disappear from the inventory display. When both items are in the two slots, the sword will appear. After this, the user can click the craft button to craft the sword and it will show up in the inventory display while the items will disappear. When the user presses L, the screen will close:

public class GameAreaDisplay extends UIComponent {
  private String gameAreaName = "";
  private Label title;
  private ImageButton craftButton;
  private Texture buttonTexture;
  private TextureRegion buttonTextureRegion;
  private TextureRegionDrawable buttonDrawable;
  private Image craftMenu;
  private ImageButton wood;
  private Texture woodTexture;
  private TextureRegion woodTextureRegion;
  private TextureRegionDrawable woodDrawable;
  private ImageButton steel;
  private Texture steelTexture;
  private TextureRegion steelTextureRegion;
  private TextureRegionDrawable steelDrawable;
  private Image weapon;
  private Group craftingGroup = new Group();
  private int count;
  List<Materials> inventory;

  public GameAreaDisplay(String gameAreaName) {
    this.gameAreaName = gameAreaName;
    ServiceLocator.registerCraftArea(this);
  }

  @Override
  public void create() {
    super.create();
    addActors();
  }

  private void addActors() {
    title = new Label(this.gameAreaName, skin, "large");
    stage.addActor(title);
  }

  public void openCraftingMenu() {
    craftMenu = new Image(new Texture(Gdx.files.internal
            ("images/Crafting-assets-sprint1/crafting table/craftingUI.png")));
    craftMenu.setPosition(Gdx.graphics.getWidth()/2 - craftMenu.getWidth()/2,
            Gdx.graphics.getHeight()/2 - craftMenu.getHeight()/2);
    craftingGroup.addActor(craftMenu);
    buttonTexture = new Texture(Gdx.files.internal
            ("images/Crafting-assets-sprint1/widgets/craftButton.png"));
    buttonTextureRegion = new TextureRegion(buttonTexture);
    buttonDrawable = new TextureRegionDrawable(buttonTextureRegion);
    craftButton = new ImageButton(buttonDrawable);
    craftButton.setPosition(craftMenu.getX() + 650, craftMenu.getY() + 130);
    craftButton.addListener(new ChangeListener() {
      @Override
      public void changed(ChangeEvent event, Actor actor) {
        if (weapon != null) {
          weapon.setPosition(craftMenu.getX() + 30, (craftMenu.getTop() - 210));
          wood.remove();
          steel.remove();
        }
      }
    });
    craftingGroup.addActor(craftButton);
    getInventory();
    entity.getEvents().addListener("check", this::displayWeapon);
    stage.addActor(craftingGroup);
    stage.draw();
  }

  private void getInventory() {
    count = 0;
    CraftingSystem craftingSystem = new CraftingSystem();
    inventory = craftingSystem.getInventoryContents();
    try {
      for (int i = 0; i < inventory.size(); i++) {
        switch (inventory.get(i)) {
          case Wood:
            woodTexture = new Texture(Gdx.files.internal
                    ("images/Crafting-assets-sprint1/materials/wood.png"));
            woodTextureRegion = new TextureRegion(woodTexture);
            woodDrawable = new TextureRegionDrawable(woodTextureRegion);
            wood = new ImageButton(woodDrawable);
            wood.setSize(50, 50);
            wood.setPosition(craftMenu.getX() + 30 + (i * 95),
                    (float) (craftMenu.getTop() - ((Math.floor(i / 4) * 100) + 210)));
            wood.addListener(new ChangeListener() {
              @Override
              public void changed(ChangeEvent event, Actor actor) {
                wood.setPosition(craftMenu.getX() + 485, craftMenu.getY() + 270);
                count++;
                entity.getEvents().trigger("check");
              }
            });
            craftingGroup.addActor(wood);
            break;
          case Steel:
            steelTexture = new Texture(Gdx.files.internal
                    ("images/Crafting-assets-sprint1/materials/steel.png"));
            steelTextureRegion = new TextureRegion(steelTexture);
            steelDrawable = new TextureRegionDrawable(steelTextureRegion);
            steel = new ImageButton(steelDrawable);
            steel.setSize(50, 50);
            steel.setPosition(craftMenu.getX() + 30 + (i * 95),
                    (float) (craftMenu.getTop() - ((Math.floor(i / 4) * 100) + 210)));
            steel.addListener(new ChangeListener() {
              @Override
              public void changed(ChangeEvent event, Actor actor) {
                steel.setPosition(craftMenu.getX() + 600, craftMenu.getY() + 270);
                count++;
                entity.getEvents().trigger("check");
              }
            });
            craftingGroup.addActor(steel);
            break;
        }
      }
    } catch (Exception e) {}
  }

  public void disposeCraftingMenu() {
    craftingGroup.remove();
  }

  private void displayWeapon() {
    if (count == 2) {
      weapon = new Image(new Texture(Gdx.files.internal
              ("images/CombatWeapons-assets-sprint1/Sword_Lvl2.png")));
      weapon.setPosition(craftMenu.getX() + 880, craftMenu.getY() + 270);
      craftingGroup.addActor(weapon);
    }
  }

  @Override
  public void draw(SpriteBatch batch)  {
    int screenHeight = Gdx.graphics.getHeight();
    float offsetX = 10f;
    float offsetY = 30f;

    title.setPosition(offsetX, screenHeight - offsetY);
  }

  @Override
  public void dispose() {
    super.dispose();
    title.remove();
  }
}

More information/javadoc go to crafting branch, under crafting package.

⚠️ **GitHub.com Fallback** ⚠️