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:
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). If you are using the code to drag the player with the mouse, remember to set Mouse → Filter to Ignore to prevent this node from blocking mouse events.
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.
Once the code is set, 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.