In this tutorial we will add a house to the game, in which the player will be able to enter to use the bed to rest and recover his health and mana points. When the player is outside the house, the whole building will be shown. When the player enters the house, the roof will be hidden and the interior will be shown.

Importing assets

First, download the images of the house by clicking on the button below:

Download “SimpleRPG House Sprites” house_sprites.zip – 5 KB

Create a new folder called House inside the Entitites folder, and copy the images you just downloaded into it. Make sure the images are imported with Filter disabled.

Creating the house scene

Create a new scene. In the Scene panel select 2D Scene to create a root node of type Node2D. Change the name of this node to House.

Add a Sprite node to House and rename it Interior. Drag the house_interior.png file into the Texture property of this node. Add another Sprite node to House and rename it Roof. Drag the house_roof.png file into its Texture property and set its Z Index to 2.

Save the scene as House.tscn in the Entitites/House folder.

Adding the house to the main scene

Return to the main scene and select the Root node. Drag the House.tscn file to the point on the map where you want to place the house (it must be an obstacle-free area, so edit the map if you don’t have a sufficiently large free area).

If you run the game now, you will see that the player will be able to walk through the house. We have to add colliders to block the player’s movement when it moves against the walls.

Adding colliders for house walls and furniture

Go back to the House scene and temporarily make Roof invisible, by clicking on the Eye icon next to the node name.

Add a StaticBody2D node to House and rename it NorthWall. Add a CollisionShape2D node to NorthWall. In the Inspector, set its Shape to New RectangleShape2D. Now, in the editor move and resize the rectangle to cover the north wall as in the following picture:

Repeat the same procedure for the east and west walls.

The south wall is divided into two parts because there is the front door of the house, so you will have to create two StaticBody2D (call them SouthWallLeft and SouthWallRight). For these two nodes, use a slightly higher rectangle to prevent the player from completely overlapping the wall when moving towards it (this will give a greater perception of three-dimensionality). You’ll get something like this:

Now we just have to create the colliders for the internal walls and for the furniture. Follow the previous procedure to create all the StaticBody2D nodes we need with the relative CollisionShape2D and RectangleShape2D. You can call the StaticBody2D nodes as you want, but make sure to call the bed Bed because we’ll need it later.

The end result will be like this:

Save the scene and try to launch the game. Now the player will be blocked by walls and objects inside the house.

Hiding and showing the roof

When the player enters the house, we want to hide the roof, and we want to show it again when the player leaves it. To do this, we will use an Area2D node to detect whether the player is inside the house or not. We will use the body_entered() and body_exited() signals to know when the player enters or leaves it.

Add an Area2D node to House. Add a CollisionShape2D node to it and set a rectangular collision shape by setting its Shape property to New RectangleShape2D. Resize the shape to cover the walkable area of the house as in the picture below:

The shape is highlighted in red

Attach a new script to the House node, call it House.gd and save it in the Entitites/House folder.

Connect the body_entered() and body_exited() signals of Area2D to the House node. The _on_Area2D_body_entered() and _on_Area2D_body_exited() functions will be created automatically.

Change this two functions to hide/show the Roof node when the player enters or leaves the house:

func _on_Area2D_body_entered(body):
	if body.name == "Player":
		$Roof.hide()

func _on_Area2D_body_exited(body):
	if body.name == "Player":
		$Roof.show()

Now make Roof visible by clicking on the Eye icon next to the node name and run the game to test if everything is working as expected.

Using the bed to rest

In all RPGs, the player can use beds to restore health and mana points. Our game is no exception, so the player can decide to sleep by pressing the attack button when he is near the bed.

Let’s start by creating a “sleep screen”. Add a new ColorRect node to CanvasLayer and call it Sleep. In the Inspector, set Color to black and Rect → Size to (320,180).

Add a RichTextLabel to Sleep. This node is used to show formatted text and allows you to animate the text with some simple animations.

In the Inspector, go to the Custom Fonts section and in the Normal Font property load the Font.tres font we created in the GUI tutorial. Set Rect → Position to (0,80) and Rect → Size to (320,20), then disable the Clip Content property.

RichTextLabel nodes support BBCode parsing to format text (click to learn all the available tags). So, go to the Bb Code section, turn on the Enabled property and copy this code to the Bb Code → Text property:

[center][wave amp=40 freq=2]ZZZ...[/wave][/center]

In this text we used two BBCode tags:

  • [center]: center the text inside the label.
  • [wave]: animate the text making it go up and down. The amp property controls how high and low the effect goes, and freq controls how fast the text goes up and down.

If you are using the code to drag the player with the mouse, remember to set Mouse → Filter to Ignore for both the Sleep and RichTextLabel nodes to prevent them from blocking mouse events.

Once everything is done, you will see the animated text on the screen:

Finally, set Sleep‘s modulate property to (255,255,255,0) to make it invisible. We’ll make it visible again using an animation.

To create the animation that will show this screen, select the Player → AnimationPlayer node. The Animation panel will automatically open at the bottom of the editor.

Click the Animation button and select New to create a new animation. Call it Sleep.

Add a new property track:

In the first window choose Sleep as the node to animate and in the second window choose the modulate property.

Now, set the animation length to 3 seconds:

Add 4 keyframes to the modulate track (right click → Insert Key on the track) at positions 0, 0.4, 2.6, 3:

You can use the zoom slider in the bottom-right of the panel to change the time scale and make it easier for you to work. If you place a keyframe in the wrong second, you can move it by dragging it along the track.

Select the two central keyframes by clicking them while holding down the shift key. Then, in the Inspector change Value to 255,255,255,255.

Now the central keyframes are white

Finally, we just have to add to the Player.gd script the code to run the animation and to restore the player’s health and mana values. In the _input() function, edit the code that handles the attack input event in this way:

if event.is_action_pressed("attack"):
	# Check if player can attack
	var now = OS.get_ticks_msec()
	if now >= next_attack_time:
		# What's the target?
		var target = $RayCast2D.get_collider()
		if target != null:
			if target.name.find("Skeleton") >= 0:
				# Skeleton hit!
				target.hit(attack_damage)
			if target.is_in_group("NPCs"):
				# Talk to NPC
				target.talk()
				return
			if target.name == "Bed":
				# Sleep
				$AnimationPlayer.play("Sleep")
				yield(get_tree().create_timer(1), "timeout")
				health = health_max
				mana = mana_max
				emit_signal("player_stats_changed", self)
				return
		# Play attack animation
		attack_playing = true
		var animation = get_animation_direction(last_direction) + "_attack"
		$Sprite.play(animation)
		# Play attack sound
		$SoundAttack.play()
		# Add cooldown time to current time
		next_attack_time = now + attack_cooldown_time

The if statement checks whether the object in front of the player is a bed. If so, the code plays the Sleep animation and after a second of delay (yield (get_tree().Create_timer(1), “timeout”) restores the health and mana values to their maximum. Finally, the player_stats_changed() signal is emitted to update the GUI.

Preventing skeletons from entering the house

The last thing we want to do is to stop the skeletons from entering the house. To do this, we use the house’s Area2D body_entered() signal to detect if a skeleton is entering through the door. We have already created the function that handles this event, so let’s modify it like this:

func _on_Area2D_body_entered(body):
	if body.name == "Player":
		$Roof.hide()
	elif body.name.find("Skeleton") >= 0:
		body.direction = -body.direction
		body.bounce_countdown = 16

How does it work? If the body that comes into contact with Area2D is a skeleton, we invert its direction of movement and set bounce_countdown to 16 to force it to move in the new direction for 4 seconds (the skeleton script recalculates the direction of movement every 250 ms, so 250 ms x 16 = 4000 ms = 4 s).

Conclusions

In this tutorial we learned:

  • How to create a house whose interiors are shown or hidden automatically when the player enters or leaves it
  • How to create a bed where the player can rest and recover his health and mana points
  • How to use the RichTextLabel node to create text with effects.

You can try what we’ve done so far by clicking here:

In the next tutorial we will add a home screen and learn how to save and load the game.

Did you enjoy this article? Then consider buying me a coffee! Your support will help me cover site expenses and pay writers to create new blog content.


13 Comments

Avatar

James · March 17, 2020 at 11:26 am

Hey Davide Pesce!

I’m still following these tutorials and still learning a lot from you.
I just wanna give your support so you know there people still following these tutorials.
I was wondering isn’t it possible to still have the roof on when the player enters the house?
I noticed that the roof disappears when the player is inside the house. Let me know on what you think about that.

Just a suggestion if there is more our player can do indoors.

Thank You for the effort you putting on this project man.

    Avatar

    Davide Pesce · March 17, 2020 at 2:15 pm

    Hi James, your support is highly appreciated, thank you very much!

    Of course, the roof can always remain visible (just remove the code that calls the hide() and show() functions from the house script). But then how do you know where the player is when is moving inside the house? Probably I’m misinterpreting your question…

Avatar

Alan · March 19, 2020 at 7:53 pm

Tutorials are amazing! Something for your followers to try at home…Skeletons should not be able to spawn inside the house. Again, really appreciate the effort that went into this. Hopefully all the aspiring game developers you have put on the path to fame remember to give you some credit…

    Avatar

    Davide Pesce · March 19, 2020 at 10:38 pm

    Thanks Alan!

Avatar

qq806418714 · April 20, 2020 at 3:54 am

You’ve done a great job! these tutorials are really useful for me!
Now I’m ready to make a game,but for a complete game, I don’t know how to switch maps.
Games usually have multiple maps,I feel this is a necessary part.
My idea now is to make a TileMapSpawner node like the tutorial at part 10.
But I’m not sure,So can you give me some suggestions?

    Avatar

    Davide Pesce · April 20, 2020 at 8:19 am

    Usually each map is stored in a different scene, and the main scene manages the switching from one map to another by instantiating them when necessary. I try to give you an example of how it could work.

    Imagine having three scenes: the Main scene, Map 1 and Map 2. When the game starts, the Main scene is loaded, and this scene in turn instantiates Map 1. The player moves through Map 1 until he reaches the passage that leads to Map 2. At this point, Map 1 sends a signal to the Main scene to notify it that the player wants to go to Map 2. When the Main scene receives the signal, it frees Map 1 and instantiate Map2.

    Scattered around my tutorials there is everything you need to implement such a system, so it’s a good exercise to review everything you’ve learned. Anyway, if you need a little help you can find me here.

      Avatar

      qq806418714 · April 20, 2020 at 10:46 am

      Thanks for the answer, I feel I should be able to achieve it!

Avatar

Dean · April 23, 2020 at 6:52 am

Amazing Tutorial Davide, it’s the first one I have found that is actually works and the steps are clear and concise.
Thanks for your efforts and really looking forward to the next parts

    Avatar

    Davide Pesce · April 24, 2020 at 7:42 am

    Thank you Dean!

Avatar

Juanma · June 5, 2020 at 9:21 pm

I just went through all your tutorial, and it is really good. Best one I have seen so far.

I have a question. The main scene is a bit cluttered with all the GUI elements. I cant see where the main character is, for example.

What would be a better way of doing this?

    Avatar

    Davide Pesce · June 7, 2020 at 7:55 pm

    Hi Juanma! If you don’t see where the main character is, you obviously have a problem with your project. The only always visible elements of the GUI are at the bottom of the screen (as you can see in the tutorial), so they do not in any way hinder the visibility of the character. Can you send me a screenshot to understand your problem?

Avatar

Gus · July 11, 2020 at 5:31 pm

Your tutorial series is mighty good! Thank you very much for it, you obviously put in a lot of work. All the instructions, the human-friendly code, the images and the videos.
You are awesome!!!

    Avatar

    Davide Pesce · July 15, 2020 at 10:54 am

    Thanks Gus! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close