NPC Class Code Team8 - UQdeco2800/2022-studio-1 GitHub Wiki

Function to create NPC entity

The function createBaseNPC() is implemented in NPCFactory

public static Entity createBaseNPC() {
        AITaskComponent aiComponent =
            new AITaskComponent()
                .addTask(new WanderTask(new Vector2(30f, 30f), 2f));
        Entity npc =
            new Entity()
                .addComponent(new PhysicsComponent())
                .addComponent(new PhysicsMovementComponent())
                .addComponent(new ColliderComponent())
                //.addComponent(new HitboxComponent().setLayer(PhysicsLayer.NPC))
                .addComponent(new EntityClassification(EntityClassification.NPCClassification.NPC))
                //.addComponent(new TextureRenderComponent(NPC_textures[index]))
                .addComponent(aiComponent);
        
        npc.setCollectable(false);

        PhysicsUtils.setScaledCollider(npc, 0.9f, 0.4f);
        return npc;
      }

The function assigns the NPC entity with basic, necessary components such as a wander task to allow NPCs to wander around Atlantis.

      public static Entity createSpecialNPC() {
        Entity NPC = createBaseNPC();
        NPC.setName("SpecialNPC");
//        AnimationRenderComponent animator =
//                 new AnimationRenderComponent(
//                         ServiceLocator.getResourceService().getAsset("images/npc_animations/npc.atlas", TextureAtlas.class));
//        animator.addAnimation("NPC", 0.1f, Animation.PlayMode.LOOP);
//        NPC.addComponent(animator);
//        NPC.getComponent(AnimationRenderComponent.class).scaleEntity();
//        NPC.getComponent(AnimationRenderComponent.class).startAnimation("NPC");
        NPC.addComponent(new TextureRenderComponent("images/npc1.png"));
        NPC.setScale(5f,5f);

          return NPC;
      }

Function createSpecialNPC is extends upon createBaseNPC, NPCs created by this function has a specific name (SpecialNPC) and texture for differentiation purposes. NPC entities with this name will have extended functionalities such as being able to conversate with the main character. Animation components were implemented but ultimately left out due to a bug.

public static Entity createNormalNPC() {
          String[] NPC_textures = { "images/npcs/NPC-V2.1.png",
              "images/npcs/NPC-V2.2.png" };

      int index = (int) ((Math.random() * (NPC_textures.length)));
      Entity NPC = createBaseNPC();
      NPC.addComponent(new TextureRenderComponent(NPC_textures[index]));
      NPC.setName("NormalNPC");
      NPC.getComponent(TextureRenderComponent.class).scaleEntity();

      return NPC;
    }

NPCs created by this function with have a texture randomly selected a texture from the NPC_textures list.

Spawning of NPCs

Spawning of NPCs is handled by the spawnNPCharacter function in ForestGameArea. The function uses createSpecialNPC and createNormalNPC from NPCFactory and to proportionally spawn these two types of NPCs. NPCs will randomly spawn in one of the four locations specified in the list NPC_SPAWNS. Once a NPC is spawned, the function calls NpcService and increments NPCNum which keeps track on number of NPCs on the map

 private void spawnNPCharacter() {
    Entity NPC;
    if(NPCNum % 3 == 0) {
       NPC = NPCFactory.createNormalNPC();
    } else {
      NPC = NPCFactory.createSpecialNPC();
    }

    ServiceLocator.getNpcService().registerNamed(String.valueOf(NPCNum), NPC);
    this.entityMapping.addEntity(NPC);
    int index = (int) ((Math.random() * (NPC_SPAWNS.length)));
    spawnEntityAt(NPC, NPC_SPAWNS[index], true, true);
    NPCNum++;
  }

Spawning of NPCs relative to number of Structures built

The function spawnNPC located in ForestGameArea spawns NPCs according to the number of Structures currently on the map and the part of Day. During the daytime (DAWN,DAY,DUSK), the function checks if the number of NPCs on the map is equal to the number of Structures on the map. If this is not the case, NPCs will be spawned will spawnNPCharacter. As the night arrives, the function will dispose of all NPC entities to avoid enemies attack. The function will call NpcService and reset NPCNum. At dawn of the following day, the NPCs will return to Atlantis.

private void spawnNPC(DayNightCycleStatus partOfDay) {
    int StructuresNum = ServiceLocator.getStructureService().getAllNamedEntities().size();
    //System.out.println("struct:"+StructuresNum);
    switch (partOfDay) {
      case DAWN:
      case DAY:
      case DUSK:

        if (NPCNum != StructuresNum) {
          //System.out.println(NPCNum);
          for (int i = NPCNum; i < StructuresNum; i++) {
            spawnNPCharacter();
          }
        }
        break;
      case NIGHT:
        // Despawn NPCs
        for (int i = 0; i < NPCNum; i++) {
          Entity NPC = ServiceLocator.getNpcService().getNamedEntity(String.valueOf(i));
          NPC.dispose();
        }

        NPCNum = 0;
        ServiceLocator.getNpcService().setNpcNum(NPCNum);
        break;
    }
  }

This function uses a listener to keep track of the current partOfDay.

    ServiceLocator.getDayNightCycleService().getEvents().addListener(DayNightCycleService.EVENT_PART_OF_DAY_PASSED,
            this::spawnNPC);

NPC Service

NpcService inherits and overrides a number of functions from EntityService, these functions have identical functionalities and therefore will not be elaborated upon. The getters and setters for NpcNum are used to keep track of number of NPCs currently on the map.

   public void setNpcNum(int num) {
        this.npcNum = num;
    }

    public int getNpcNum() {
        return npcNum;
    }

Determine if NPC is clicked

The npcClicked function uses the camera component in the game to determine the mouse position when mouse is clicked. If this position matches the position of an NPC, this function will return true. If the NPC being clicked on has the name "Special NPC", a conversation pop up will appear and the isVisible Boolean variable will be set to true to indicate that the conversation pop up is currently visible. If mouse is not clicked on an NPC and isVisible is set to true, the conversation pop up will be removed

  public static boolean npcClicked(int screenX, int screenY) {
        Entity camera = ServiceLocator.getEntityService().getNamedEntity("camera");
        CameraComponent camComp = camera.getComponent(CameraComponent.class);
        Vector3 mousePos = camComp.getCamera().unproject(new Vector3(screenX, screenY, 0));
        Vector2 mousePosV2 = new Vector2(mousePos.x, mousePos.y);
        mousePosV2.x -= 0.5;
        mousePosV2.y -= 0.5;
//        System.out.println("mouse x:" +mousePosV2.x);
//        System.out.println("mouse y:" +mousePosV2.y);

        if (isVisible) {
            Conversation.remove();
            isVisible = false;
        }

        for (int i = 0; i < ServiceLocator.getNpcService().getNpcNum(); i++) {
            Entity NPC = ServiceLocator.getNpcService().getNamedEntity(String.valueOf(i));
            float xPos = NPC.getPosition().x;
            float yPos = NPC.getPosition().y;


            if (xPos+1 < mousePosV2.x && mousePosV2.x < xPos+3) {
                if (yPos+0.5 < mousePosV2.y && mousePosV2.y < yPos+4) {
                    //System.out.println("npc clicked");
                    //initiate conversation
                    if(Objects.equals(NPC.getName(), "SpecialNPC")) {
                        Conversation = ServiceLocator.getEntityService().getNamedEntity("ui").getComponent(MainGameNpcInterface.class).makeUIPopUp(true);
                        isVisible = true;
                    }
                    return true;
                }
            }

        }

        return false;
    }

Conversation UI

The Conversation UI is implemented in the function makeUIPopUp in the class MainGameNPCInterface. The function sets the appropriate background and NPC image and randomly selects a dialogue from the NPC_dialogues list. If a specific dialogue (index 8) is selected, the player will be rewarded with a small amount of gold.

public Table makeUIPopUp(Boolean status) {
            float uiWidth = 950f;
            float uiHeight = 300f;
            float screenWidth = Gdx.graphics.getWidth();
            isVisible = status;

            ConversationUI = new Table();
            ConversationUI.setSize(uiWidth, uiHeight);
            ConversationUI.setPosition(screenWidth/4,0);
            ConversationUI.setVisible(isVisible);

            Texture colour = new Texture(Gdx.files.internal("images/pop-up background.png"));
            Drawable backgroundColour = new TextureRegionDrawable(colour);

            String[] NPC_dialogues = {
                    "The fate of Atlantis is in your hands",
                    "You are the chosen one....... Chiron" ,
                    "The fate of Atlantis is in your hands",
                    "You are the chosen one....... Chiron" ,
                    "The fate of Atlantis is in your hands",
                    "You are the chosen one....... Chiron" ,
                    "The fate of Atlantis is in your hands",
                    "You are the chosen one....... Chiron" ,
                    "A little offering for you, 5 gold"
            };

            int index = (int) ((Math.random() * (NPC_dialogues.length)));

            if(index == 8){
                Entity player = ServiceLocator.getEntityService().getNamedEntity("player");
                player.getComponent(InventoryComponent.class).addGold(5);
                PlayerStatsDisplay.updateItems();
            }

            Label npcLabel = new Label(NPC_dialogues[index], skin, "large");
            NpcImage = new Image(ServiceLocator.getResourceService().getAsset("images/NPC convo.png", Texture.class ));
            LeftTable = new Table();
            LeftTable.left();
            LeftTable.padLeft(10f);
            LeftTable.padRight(10f);
            //LeftTable.setFillParent(true);

            LeftTable.add(NpcImage).size(175f,200f).center();
            ConversationUI.add(LeftTable).size(225f, 300f);
            ConversationUI.add(npcLabel).size(500f,200f).center().padRight(50f);
            ConversationUI.setBackground(backgroundColour);
            stage.addActor(ConversationUI);
            return ConversationUI;
        }

The Sequence Diagram for this is available here