To make a break from the hockey game, so I don't burn out, I decided to try another item of my wanted To-Do list. In other words, this entry is (as the title says) for making a dungeon with procedural generation.
First we need a basic idea of what we are doing, so here are two definitions:
About the topic at hand:
https://en.m.wikipedia.org/wiki/Procedural_generation
And about the selection made (which is one that requires less resources than others while still being good enough):
https://en.m.wikipedia.org/wiki/Simplex_noise and https://docs.godotengine.org/en/3.1/classes/class_opensimplexnoise.html
To put it simply, we are going to make an algorithm that follows a path to visit all the tiles in the screen, meanwhile, it will be filling them (or not) according to the parameters given. You can see the video explanation I took as a practical example for the base of today's minigame:
https://youtu.be/SBDs8hbs43w?si=C5VS1iVUUF-1mRhA And later we will add some features to interact with.
Procedural code for generating the dungeon, script attached to the main node of the scene:
First we need a basic idea of what we are doing, so here are two definitions:
About the topic at hand:
https://en.m.wikipedia.org/wiki/Procedural_generation
And about the selection made (which is one that requires less resources than others while still being good enough):
https://en.m.wikipedia.org/wiki/Simplex_noise and https://docs.godotengine.org/en/3.1/classes/class_opensimplexnoise.html
To put it simply, we are going to make an algorithm that follows a path to visit all the tiles in the screen, meanwhile, it will be filling them (or not) according to the parameters given. You can see the video explanation I took as a practical example for the base of today's minigame:
https://youtu.be/SBDs8hbs43w?si=C5VS1iVUUF-1mRhA And later we will add some features to interact with.
Procedural code for generating the dungeon, script attached to the main node of the scene:
´´´
extends Node2D
var noise
var map_size = Vector2(80, 60)
var grass_cap = 0.5
var road_caps = Vector2(0.3, 0.05)
var enviroment_caps = Vector3(0.4, 0.3, 0.04)
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()
add_characters()
func make_grass_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < grass_cap:
$Grass.set_cell(x,y,0)
$Grass.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func make_road_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < road_caps.x and a > road_caps.y:
$Roads.set_cell(x,y,0)
$Roads.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func make_enviroment_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < enviroment_caps.x and a > enviroment_caps.y or a < enviroment_caps.z:
var chance = randi() % 100
if chance < 2:
var num = randi() % 4
$Enviroment.set_cell(x,y, num)
func make_background():
for x in map_size.x:
for y in map_size.y:
if $Grass.get_cell(x,y) == -1:
if $Grass.get_cell(x,y-1) == 0:
$Background.set_cell(x,y,0)
$Background.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func add_characters(): # Needs a few touches to avoid spawning in pits or colliders
# 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()
$Player.global_position = Vector2(rng.randi_range(0,WEIGHT) , rng.randi_range(0,HEIGHT) )
$NPC.global_position = Vector2(rng.randi_range(0,WEIGHT) , rng.randi_range(0,HEIGHT) )
```
var noise
var map_size = Vector2(80, 60)
var grass_cap = 0.5
var road_caps = Vector2(0.3, 0.05)
var enviroment_caps = Vector3(0.4, 0.3, 0.04)
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()
add_characters()
func make_grass_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < grass_cap:
$Grass.set_cell(x,y,0)
$Grass.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func make_road_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < road_caps.x and a > road_caps.y:
$Roads.set_cell(x,y,0)
$Roads.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func make_enviroment_map():
for x in map_size.x:
for y in map_size.y:
var a = noise.get_noise_2d(x,y)
if a < enviroment_caps.x and a > enviroment_caps.y or a < enviroment_caps.z:
var chance = randi() % 100
if chance < 2:
var num = randi() % 4
$Enviroment.set_cell(x,y, num)
func make_background():
for x in map_size.x:
for y in map_size.y:
if $Grass.get_cell(x,y) == -1:
if $Grass.get_cell(x,y-1) == 0:
$Background.set_cell(x,y,0)
$Background.update_bitmask_region(Vector2(0.0, 0.0), Vector2(map_size.x, map_size.y))
func add_characters(): # Needs a few touches to avoid spawning in pits or colliders
# 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()
$Player.global_position = Vector2(rng.randi_range(0,WEIGHT) , rng.randi_range(0,HEIGHT) )
$NPC.global_position = Vector2(rng.randi_range(0,WEIGHT) , rng.randi_range(0,HEIGHT) )
```
For those who have problems with the tiles collision, they can be edited following the image bellow:
1- Go to the TileMap node (You can do so in the scene section)
1- Go to the TileMap node (You can do so in the scene section)
4 - Go to your desire Tiles and modify whatever you need.
Once we have our terrain, we can start digging about the interactions with it. To do that, we want a character controlled by us, so here's the basic code for it:
```
extends KinematicBody2Dclass_name Player
export (int) var speed = 100
# onready var anim = $AnimationPlayer
# onready var animTree = $AnimationTree
# onready var animTree_playback = $AnimationTree.get("parameters/playback")
var velocity = Vector2()
func get_input():
velocity = Vector2.ZERO
if Input.is_action_pressed("move_right"):
velocity.x += 1
if Input.is_action_pressed("move_left"):
velocity.x -= 1
if Input.is_action_pressed("move_down"):
velocity.y += 1
if Input.is_action_pressed("move_up"):
velocity.y -= 1
velocity = velocity.normalized() * speed
func _physics_process(_delta):
get_input()
velocity = move_and_slide(velocity)
```
After being able to walk around, It's time to add an NPC to interact with:
```
extends KinematicBody2D
var talk = false setget set_hiden
func _process(_delta):
if Input.is_action_just_pressed("ui_accept"):
var player = get_parent().get_node("Player/Area2D")
if get_node("Area2D").overlaps_area(player):
self.talk = !talk
func set_hiden(value):
talk = value
# Show/Hide dialogue
$CanvasLayer.visible = talk
```
extends KinematicBody2D
var talk = false setget set_hiden
func _process(_delta):
if Input.is_action_just_pressed("ui_accept"):
var player = get_parent().get_node("Player/Area2D")
if get_node("Area2D").overlaps_area(player):
self.talk = !talk
func set_hiden(value):
talk = value
# Show/Hide dialogue
$CanvasLayer.visible = talk
```
You could also make that an RPG turn based combat, making the conversation a transition to this scene if you want:
https://youtu.be/ifXGvlAn0bY?si=1KjngnMeaoJFfnfS (code here: https://github.com/jontopielski/Turn-Based-Combat )
Finally, there are a few things more, like the HUD to let the player see inventory and other "little" stuff. But, that's out of the scope of this entry, besides including those explanations would make this even longer. I will make the HUD, a shop, an enemy, and two collectables (so it has all the basics) in a future entry, and then I'll release the full project for those who want to use it as a base.
https://youtu.be/ifXGvlAn0bY?si=1KjngnMeaoJFfnfS (code here: https://github.com/jontopielski/Turn-Based-Combat )
Finally, there are a few things more, like the HUD to let the player see inventory and other "little" stuff. But, that's out of the scope of this entry, besides including those explanations would make this even longer. I will make the HUD, a shop, an enemy, and two collectables (so it has all the basics) in a future entry, and then I'll release the full project for those who want to use it as a base.
No comments:
Post a Comment