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.
First, download the images of the house by clicking on the button below:
Download “SimpleRPG House Sprites”house_sprites.zip – Downloaded 3036 times – 5.22 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:
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.
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).
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.
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.
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…
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…
Davide Pesce · March 19, 2020 at 10:38 pm
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？
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.
qq806418714 · April 20, 2020 at 10:46 am
Thanks for the answer, I feel I should be able to achieve it!
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
Davide Pesce · April 24, 2020 at 7:42 am
Thank you Dean!
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?
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?
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!!!
Davide Pesce · July 15, 2020 at 10:54 am
Thanks Gus! 🙂
Thomas · November 15, 2020 at 11:04 pm
Going through this tutorial makes a lot of fun. At least i’m not a programmer, so sometimes it’s really hard and my respect of peaple or teams who are ceating games is growing more and more.
At this point, I maneged all without any big mistakes at the and. Finding and fixing error, I made durcing following this tutorial was sometimes very long process 🙂
Now I found somthing, I’m not able to fix:
the skeletons are spawing nto only outside of my house, but also insde. What I don’t like 😉
I’d like to prevent this behavior and want to check the spawning position in the skeleton spawner, if the house is positioned on the spwaning position in the same way like for the trees/grass check, but unfortunately I don’t knwo howto make this. The house is no tilemap. Is there a methode where I can check if the postion is, where the house is located. So I don’t have to fix Code When I try to move the house around on the map?
Thank you and best regards
Davide Pesce · November 21, 2020 at 1:55 pm
I try to suggest you a way to solve your problem. If you have problems implementing it, feel free to write me (it may take me a while to reply but sooner or later I will).
1) You need to get a reference to House using get_node(), the same way you get references to TileMap and Tree TileMap.
2) Now that you have the reference, you can know the position of the center of the house using global_position.
3) In the test_position function, return that the position is invalid if it’s inside a rectangle around the center of the house. The rectangle must be the size of the house (actually I’d recommend a little bit bigger). If you want a simpler solution, you can just check the distance from the center of the house (just choose a sufficiently large distance).
Thomas · November 22, 2020 at 8:51 am
thank you for this advice. I will try to implement it in the way you suggest. Hopefully my limited programmer abilities will not be the limit. It sounds I could be able to do it 🙂
Thank you for your help
Thomas · November 24, 2020 at 10:27 pm
Following your Advice I implemented it. I’m sure there is enough room for improvement and more elegance, but I’m a kind proud of it, first godot code without copying code from tutorials 🙂
I wanted to do it, in that way, that I can move the house around without any changes in code.
It seems to work, a lot of debug output showed that skeletons tried to spawn in the house and with checking the coordinates I could prevent it.
How did I do it?
First I added a “ColorRect” Node to the House Node and made the size big enough around the whole house. I made the color transparent.
in SkeletonSPawnder.gd I added the following codelines:
under “extends Node2D”
in “func _ready():”
house = get_tree().root.get_node(“Root/House”)
no_spawn_area = get_tree().root.get_node(“Root/House/NoSpawnArea”)
in “func test_position(position : Vector2):”
# check for house
var house_pos = house.get_position()
var no_inhouse_spawn = ( ! (\
position.x > (house_pos.x + no_spawn_area.margin_left) && \
position.x (house_pos.y + no_spawn_area.margin_top) && \
position.y < (house_pos.y + no_spawn_area.margin_bottom)))
and I changed the "return" to
# position is valid if all values are true
return grass_or_sand and no_trees and no_inhouse_spawn
If you want you can take a look at https://github.com/tlo8640/SimpleRPG
Impressive, only a few lines of code were necessary to implement it.
(If you watch into my code, please ignore the fish, i'm still trying… :-))
Comments are closed.