Wednesday, October 25, 2023

Dungeon action

 This is a continuation of the "Procedural dungeon", it will contain a guide to make mainly: collectable items for an inventory (with a HUD), one enemy, and it will lead the path to finish the mini-game with an in-game shop (no outside currency at all).

Before going into the new stuff for the mini-game, a few bug fixing have to be made upon the previous job:

0 - Write the tile sizes for the map properly,
`var map_size = Vector2(960/16,544/16) # Display | Window Size / tile size`

1 - To avoid the player moving while the talking text is being displayed, a new variable
for the player script is added, ` export var canMove = true `. In addition, now the NPC
script will modify that accordingly:
```
func set_hiden(value):
    talk = value
    # Show/Hide dialogue
    $CanvasLayer.visible = talk
    if talk == true :
        get_parent().get_node("Player").canMove = false
    else :
        get_parent().get_node("Player").canMove = true
```

2 - The spawning problem in the player and NPC:
This adds extra complexity to the game and code, due to that and the nature of this project I've only included the most obvious cases, but there are more. To put it simply, we're only going to check if a background (dark pit) exists in the spawning position [If you want to do an exercise, check if characters overlap to add that case].

Tip: Add the objects you want to spawn into a group, so you can automatize (help in the screenshot).
 


```
func add_characters():
    var pos = Vector2()     # Spawn position
    for obj in get_tree().get_nodes_in_group("groupSpawn"):
        var spawn = false
        pos = spawn(pos)
        while canSpawn(pos) == false:
            pos = spawn(pos)
        obj.global_position = pos


func canSpawn(char_pos):
    # Check if the character is spawning in a good tile or not
    var cell_coordinates = get_node("Background").world_to_map(char_pos)
    var cellid = get_node("Background").get_cellv(cell_coordinates)
    if cellid == -1 :
        true
    else :
        false
    
func spawn(pos):
    # Screen size HEIGHT WEIGHT
    var HEIGHT = get_viewport().get_visible_rect().size.y
    var WEIGHT = get_viewport().get_visible_rect().size.x
    var rng = RandomNumberGenerator.new()
    rng.seed = randi()
    pos = Vector2(rng.randi_range(0,WEIGHT) , rng.randi_range(0,HEIGHT) )
    return pos
```


After those fixes, we need the base for the HUD ready to show how many items we have.
We've also added a Dictionary structure here to track the objects in the inventory.
 


```
extends CanvasLayer

var inventory : Dictionary = {
    "item0Number" : 0,
    "item1Number" : 0,
}
```


Now, since I want to learn new ways, I will follow this method for collectables (Using the procedurally generated tiles from the previous step) and their current position in the tilemap. Like here, https://youtu.be/1Fplm1Mkxb8
Assets for this part: https://gfragger.itch.io/magic-market

TIP: Change the cell name, so you can reference it in your script, check these 2 images:
    1 - Go to your tileset Node, and click on TileMap, then on Tileset
    2 - Select the tile you want to rename and go to TileSetEditorContext


Add variables for the collectables in your script dungeon2D.gd
 
```
onready var collectibles : TileMap = $Collectibles
var collectibles_caps = Vector2(1, 0.5)
var collectibles_chance = 10 # percent out of 100 that an item will be placed
```

And add the methods to procedurally generate them, in the same script. We will also make the collectable tracking process in this script. You can see the changes in the code for the dungeon2D.gd script methods below:
 
```
func _ready():
    randomize()
    noise = OpenSimplexNoise.new()
    noise.seed = randi()
    noise.octaves = 1.0
    noise.period = 12
#    noise.persistence = 0.7
    make_grass_map()
    make_road_map()
    make_enviroment_map()
    make_background()
    make_collectibles_map()
    $HUD/Label1.text = str(0)
    $HUD/Label2.text = str(0)
    add_characters()
    
func _physics_process(delta):
    check_player_clear_collectibles()
    
# Place random rocks on stone, or flowers on dirt
func make_collectibles_map():
    for x in map_size.x:
        for y in map_size.y:
            var  a = noise.get_noise_2d(x,y)
            if a < collectibles_caps.x and a > collectibles_caps.y:
                var chance = randi() % 100
                if chance < collectibles_chance:
                    collectibles.set_cell(x,y,1) # item1Number
            else :
                var chance = randi() % 100
                if chance < collectibles_chance:
                    collectibles.set_cell(x,y,0) # item2Number
# If player is on top of rock or flower clear item
func check_player_clear_collectibles():
    var cell_coordinates = collectibles.world_to_map($Player.global_position)
    var cellid = collectibles.get_cellv(cell_coordinates)
    if cellid >-1 :
        if collectibles.tile_set.tile_get_name(cellid) == "item0Number":
            $HUD/.inventory["item0Number"] += 1
            $HUD/Label1.text = str($HUD/.inventory["item0Number"])
        elif collectibles.tile_set.tile_get_name(cellid) == "item1Number":
            $HUD/.inventory["item1Number"] += 1
            $HUD/Label2.text = str($HUD/.inventory["item1Number"])
        collectibles.set_cellv(cell_coordinates, -1)
    
```
WARNING: Don't forget to add the objects in your map in the proper transform_position, so the grid and global coordinates are the same for both. Otherwise, if you want them to be collected or to chase you. They won't have the same position, and it won't be as you expect. (This has to be done in both, the main scene and your object scene. So be careful when editing)
> The cell size of the objects can also create weird results if they are not resized accordingly

Example in the screenshot:
(With a distance in the origin grid map, the global_position 0,0 in both of them will look like this).

"Look!! A dragon!! It comes after our treasure!!"

Time to make an enemy for our game. A new NPC scene, with different script, movements, and actions. For now, the dragon will only chase us (we can add more features later).
Example, to visualize, here: https://youtu.be/1e_dmDW73IQ?t=395
Assets: https://theartofnemo.itch.io/free-rpg-monsters-dragon-evolutions

Code to make the dragon chase us (this is the same code you would use if you want a companion NPC to follow you).
Obviously, this code is for the Dragon script, with its own node (you can copy the other NPC and add this code as a way to test it)

```
extends KinematicBody2D

onready var speed = get_parent().get_node("Player").speed

func _physics_process(delta):
    var velocity = Vector2.ZERO
    var direction = get_parent().get_node("Player/Area2D/CollisionShape2D").global_position
    velocity = self.global_position.direction_to(direction) * speed
    velocity = move_and_slide(velocity)
```

The next part should be the shop. However, to keep these entries short enough, this will be in the next entry (Hopefully that's the last one of this procedural mini-dungeon project).

No comments:

Post a Comment