Godot3 game engine entry ten: introduce some commonly used nodes and develop a small game (middle)

Godot3 game engine entry ten: introduce some commonly used nodes and develop a small game (middle)

Godot3 game engine entry ten: introduce some commonly used nodes and develop a small game (middle)

2018-12-05 by Liuqingwen | Tags: Godot | 4 Hits

I. Introduction

In the last article , we studied and explored several commonly used new nodes, and by the way, we also learned about the usage of several important keywords in the GDScript script, and finally summarized several so-called "best practices" that I personally think are more practical ", the purpose of writing so much is to serve this and the next: we use Godot 3.1 Alpha2 to make a small game.

This game is very simple, and there are many similar cases on the Internet. I originally planned to only need two articles up and down. Later I found that the entire article seemed "too long" after adding the code. If you shorten the length by deleting some codes , And very unfriendly to novices, so I will add one more article and divide it into three articles: "Up-Middle-Down".

Reminder: There will be a lot of code in the second and second chapters. If you are interested in this game and you are already getting started, I recommend you to download the source code and run it directly in my Github repository , or check it again if you encounter a problem. The text is more appropriate.

Main content: Analyze and make a complete mini game (Part 2)
Reading time: 12 minutes
Permalink: liuqingwen.me/blog/2018/1...
Series homepage: liuqingwen.me/blog/tags/G...

2. the text

The goal of this article

  1. Understand the production of several main scenes in the learning game
  2. Write code to implement the logic of related functions in the game
  3. A development process of a complete game project

Main scene

This is a simple "gold coin collecting game". The main ideas and gameplay of the game design are roughly as follows:

  • Players can run anywhere in the free world, and they can collect their beloved gold coins in their pockets
  • Players must avoid being stabbed by the cactus, which is the only physical obstacle in the game
  • Each level has a timeout time design, the timeout game is over, the gold coins can be collected within the specified time to enter the next level
  • A special "energy coin" is randomly generated in each level, and the player can collect energy to extend the timeout period

Well, time is running out, get in the car and set off!

1. Player sub-scene

The player sub-scene is the core game element of this project, which can be said to be the soul of the mini game. The production of the player sub-scene is very simple: take the collision body Area2D as the root node, add a Sprite picture sprite, a CollisionShape2D to draw the collision area, an AnimationPlayer node to make an animation, and an AudioStreamPlayer audio stream playback node. If you are not familiar with the use of these nodes, you can refer to my previous article.

In addition, because I made the player's animation picture into a SpriteSheet sprite atlas, when making the animation, you need to pay attention to the display area of the picture. The player has three animation states, all of which are relatively simple. Refer to the following:

2. Coin/Cactus/Power gold coin/obstacle/energy sub-scene

I put these three small scenes together to discuss, because their structure is very simple and very similar, and they all serve the "players" in the game. The production of the three sub-scenes is clear at a glance, with single functions and independent of each other, which is also in line with our best practice principle of maintaining the independence of the scenes as much as possible . In addition, in the management of game resources, I put these three scenes and related resources (pictures) of the scenes under the Items folder.

It should be noted that the LifeTimer time node in the energy coin scene means that the gold coin will automatically disappear within the specified time, and the appearance time of the energy coin is not controlled by yourself. Don't confuse it here. It will be introduced in the code later.

3. UI interface elements

The control sub-scene is mainly used for interface display, mainly including: the number of gold coins, remaining time, start button, text information display, etc. Here I use the MarginContainer container and HBoxContainer/VBoxContainer to typeset the interface elements. Remind novice friends: Setting the margin of MarginContainer needs to be set under the Custom Constants property.

In addition, the UI sub-scene is also used to receive keyboard input from the player, and to control some basic logic of the game: start, pause, retry, etc. We will implement these in the code.

4. The main game scene

This is the most important scene in the game, and it is also the root scene that contains and coordinates multiple sub-scenes. You can manually add other nodes or sub-scenes to the main scene of the game, or you can add any number of sub-scenes, such as gold coins, through code. At the same time, the main scene is responsible for and handles the communication links between each sub-scenario, as a commander-in- chief to let each sub-scenario perform its duties, and obtain and process their respective related tasks in time.

It is worth noting that: I put the obstacle scene ( Cactus ) as a child node under the Path2D path node, which is the blue path in the figure. The CoinContainer in the scene is an empty node, which serves as a container for dynamically generated gold coin nodes.

Logic and code

Code can be added to each node in Godot, and only one script can be associated at most. Generally, the function of the sub-scene is relatively single. We give priority to adding a script to the root node of the sub-scene, and other nodes can be added as required. The thing is: the public methods that need to be exposed in the sub-scene for other scenes to call are best written in the script code of the root node .

In addition, there is not only one way to implement game related functions and logic codes. You can write codes according to your own needs, design principles, game rules, etc.

Note: The inspiration and picture resources of this little game are all from the book "Godot Engine Game Development Projects" . I refer to its code, but my design method is slightly different from it, such as dealing with the collision between players and gold coins. There are two logical ways to detect collisions and call Coin in the Player scene , or detect collisions and call the Player method in the Coin gold coin scene. The author of this book adopted the former, and I chose the latter. My point is: game elements serve players, and players don t need to care about what elements are in the game world. Of course, the results are exactly the same.

Now I pick the game's main code is posted for your reference reading, if they do not know where you can always read my previous article, or by directly Godot editor F4Search to view the API, I believe with The comments in the script make it no problem to understand the specific logic of the code.

1. Player.gd

extends Area2D

# signal group
signal coin_collected(count) #  
signal power_collected(buffer) #  
signal game_over() #  

# export
export(int) var moveSpeed = 320
export(AudioStream) var coinSound = null
export(AudioStream) var hurtSound = null
export(AudioStream) var powerSound = null

# onready
onready var _animationPlayer = $AnimationPlayer
onready var _audioPlayer = $AudioStreamPlayer
onready var _sprite = $Sprite

# enum, constant

# variable
var isControllable = true setget _setIsControllable #  
var _coins = 0 #  
var _boundary = {minX = 0, minY = 0, maxX = 0, maxY = 0} #  

# functions
func _ready():
    var scale = _sprite.scale
    var rect = _sprite.get_rect()

    #  
    _boundary.minX = - rect.position.x * scale.x
    _boundary.minY = - rect.position.y * scale.y
    _boundary.maxX = ProjectSettings.get('display/window/size/width') - (rect.position.x + rect.size.x) * scale.x
    _boundary.maxY = ProjectSettings.get('display/window/size/height') - (rect.position.y + rect.size.y) * scale.y

func _process(delta):
    #  
    var hDir = int(Input.is_action_pressed('right')) - int(Input.is_action_pressed('left'))
    var vDir = int(Input.is_action_pressed('down')) - int(Input.is_action_pressed('up'))
    var velocity = Vector2(hDir, vDir).normalized()
    self.position += velocity * moveSpeed * delta
    self.position.x = clamp(self.position.x, _boundary.minX, _boundary.maxX)
    self.position.y = clamp(self.position.y, _boundary.minY, _boundary.maxY)

    if hDir != 0:
        _sprite.flip_h = hDir < 0
    if hDir != 0 || vDir != 0:
        _animationPlayer.current_animation = 'run'
    else:
        _animationPlayer.current_animation = 'idle'

# isControllable set 
func _setIsControllable(value):
    if isControllable != value:
        isControllable = value
        self.set_process(isControllable)
        _animationPlayer.current_animation = 'idle' if ! isControllable else _animationPlayer.current_animation

#  
func restart(pos):
    _coins = 0
    self.position = pos

#  
func collectCoin(num = 1):
    _coins += num
    _audioPlayer.stream = coinSound
    _audioPlayer.play()
    self.emit_signal('coin_collected', _coins)

#  
func collectPower(buffer):
    _audioPlayer.stream = powerSound
    _audioPlayer.play()
    self.emit_signal('power_collected', buffer)

#  
func hurt():
    _animationPlayer.current_animation = 'hurt'
    _audioPlayer.stream = hurtSound
    _audioPlayer.play()
    self.set_process(false)
    self.emit_signal('game_over')
 

The code part of the player scene is relatively large. Here I have specifically indicated my source code writing habits. Generally, maintaining a good code style is conducive to the debugging and function expansion of the game. The coding sequence I am used to in the code is:

  1. signal/group signal, group write code file first
  2. export is followed by related variables that can be edited in the editor
  3. onready mainly represents some references to nodes in the scene
  4. enum/constant enumeration, constant light definition part (no actual code)
  5. variable common variable definition section (public, private)
  6. The last part of functions is the method function definition part (public, private)

Also pay attention to some small details about the function part. There are public methods and private methods in the GDScript script. The location of these methods can be arbitrary, as long as you look comfortable. I will briefly explain a few key points:

  • self.set_process(false)This method can pause or turn on _process(delta)the method of operation is similar to suspend part of the game
  • self.emit_signal('power_collected', buffer) The method of transmitting the signal has been discussed, but here is an additional parameter
  • _audioPlayer.stream = xxxPlayer uses only one audio scene nodes, by setting different streamcan play different sound audio streams

Please refer to the notes for other parts.

2. Coin.gd

extends Area2D

#  
export var playerName = 'Player'
#  
export var obstacleName = 'Cactus'

onready var _collisionShape = $CollisionShape2D

func _on_Coin_area_entered(area):
    #  
    if area.name == playerName && area.has_method('collectCoin'):
        _collisionShape.disabled = true
        area.collectCoin()
        self.queue_free()
    #  
    elif area.name == obstacleName:
        self.queue_free()
 

The gold coin node is very simple and the code is very concise. The main function is: the player will automatically disappear after the player collects, and the player's collection function will be called at the same time collectCoin(). In order to prevent call errors, I judged whether the player has this method in the code.

3. Cactus.gd

extends Area2D

export var playerName = 'Player'

func _on_Cactus_area_entered(area):
    #  hurt 
    if area.name == playerName && area.has_method('hurt'):
        area.hurt()
 

This is the simplest sub-scene! The rule of the game is: after the player encounters an obstacle (cactus), the player receives damage and the game ends. Logic code can reference Player scene hurt()method.

4. Power.gd

extends Area2D

export var playerName = 'Player'
export var power = 2 #  

onready var _collisionShape = $CollisionShape2D
onready var _sprite = $Sprite
onready var _timer = $LifeTimer
onready var _tween = $DisappearTween

#  Tween 
func _startTween():
    _tween.interpolate_property(_sprite, 'modulate', Color(1.0, 1.0, 1.0, 1.0), Color(1.0, 1.0, 1.0, 0.0), 0.25, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
    _tween.interpolate_property(_sprite, 'scale', _sprite.scale, _sprite.scale * 4, 0.25, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
    _tween.start()

func _on_Power_area_entered(area):
    #  
    if area.name == playerName && area.has_method('collectPower'):
        _collisionShape.disabled = true
        area.collectPower(power)
        _timer.stop()
        _startTween()

#  
func _on_LiftTimer_timeout():
    self.queue_free()

#  
func _on_Tween_tween_completed(object, key):
    self.queue_free()
 

Like gold coins and obstacles, it is also a very simple sub-scene, but we use the Tween node to implement the disappearing animation of energy coins with code. For the Tween node, please refer to the previous article . For the definition of each parameter in the method, you can directly refer to the official API documentation.

other parts

See the next part for the code and summary of other parts! To be continued...

My blog address: liuqingwen.me , welcome to follow my WeChat public account :

Godot

Comments: