Thursday, March 28, 2019

18. OS execute

We can run commands using OS.execute function.


The root node is a Node with sole child, a Label.


This script is attached to the Node.



extends Node

var label

func _ready():
    label= get_node("Label")
    label.align = Label.ALIGN_CENTER
    label.valign = Label.VALIGN_CENTER
    var output = []
    var pid = OS.execute('csound', ["--version"], true, output)
    var result = ""
    for i in range(122):
        result += output[0][i]
    label.set_text(result) 


We assume that csound is installed on the system. This is the first 122 characters on my version:


17. AnimatedSprite

We use a set of images from the website OpenGameArt for different animations mentioned on this site.



We have a Node with child of Label and Node2D, which has its child an AnimatedSprite. The Node2D is there, so we may set its scale of 0.5, 0.5. Thus we do not have to change scale of the AnimatedSprite to reduce image sizes. We have to rename the nodes as 'node2d', 'anim_sprite' and 'label' and position the label and image so they can be viewed.


For the Animated Sprite we add two actions, Idle-Normal and Idle-Blinking after creating the new SpriteFrames. There are many other set of sequences, but they were not used. Below, we show the sequence for Idle-Blinking:



We add this script to Node.



extends Node

var anim_sprite
var label

func _ready():
    anim_sprite = get_node('node2d/anim_sprite')
    label = get_node("label")

func _process(delta):
    if Input.is_action_pressed('ui_left'):
        anim_sprite.play("idle_blinking")
        label.set_text(anim_sprite.animation)
    elif Input.is_action_pressed('ui_right'):
        anim_sprite.play("idle_normal")
        label.set_text(anim_sprite.animation)
 


We have this output for the blinking case (left arrow pressed):


16. RayCast2D

We will have 3 scenes. The first one is a plain Node called 'World.tscn'. The second is a KinematicBody2d ('Player') and third is a StaticBody2d ('Enemy')


Underneath both the 'Player' and 'Enemy' we have a Sprite, and CollsionShape2D. The Sprite for both is the Godot icon, however for the 'Enemy' one, we set the modulate property to red. The collision shape is a rectangle matching the extents of Sprite.


For the 'Player', we have four RayCast2D nodes as children. They are named Left, Right, Down, and Up. We have to check the Enable in the Inspector and change cast to, for Left, Right, and Up. For Down, the default value is ok. For Up, set it to (0,-50). For Right, set it to (50,0), and Left to (-50,0). We should be able to see the arrows pointing in correct direction. We should enable visibility on only 1 of the nodes at a time so we can see the individual rays pointing in the correct direction.


Finally to the 'World', we attach this script:



extends Node

const SPEED = 100.0

onready var Player = preload("res://Player.tscn")
onready var Enemy = preload("res://Enemy.tscn")
var player
var enemy

func _ready():
    setup()

func _process(delta):
    var motion = Vector2(0.0, 0.0)
    if Input.is_action_pressed("ui_right"):
        motion.x = SPEED
    elif Input.is_action_pressed("ui_left"):
        motion.x = -SPEED
    elif Input.is_action_pressed("ui_down"):
        motion.y = SPEED
    elif Input.is_action_pressed("ui_up"):
        motion.y = -SPEED
    player.move_and_slide(motion)
    var left = player.get_node("Left")
    var right = player.get_node("Right")
    var up = player.get_node("Up")
    var down = player.get_node("Down")
    if left.is_colliding():
        print("left")
    elif right.is_colliding():
        print("right")
    elif up.is_colliding():
        print("up")
    elif down.is_colliding():
        print("down")
    else:
        print("Not colliding")
 
func setup():
    player = Player.instance()
    add_child(player)
    player.position = Vector2(100.0, 100.0)
    enemy = Enemy.instance()
    enemy.position = Vector2(200.0, 200.0)
    add_child(enemy)


If we are to left of the 'Enemy' and such that the ray intersects (that is close enough), the Right ray will collide. We set the window as (300,300) in Project Settings. This shows the output including the text:


15. Tween

Any property can be animated by the Tween node. To a Node, we attach a script to animate the scale property of the sprite if either up arrow is pressed or down arrow is pressed.


In the _process function, we see if up or down have just been pressed. If they are, we either start the scale up animation or scale down animation. However we only start if no animations are running.


We create Sprite Node and two Tween nodes using new(). Then this script keeps monitoring for input to start animation:



extends Node

var sprite = Sprite.new()
var tween_up = Tween.new()
var tween_down = Tween.new()

func _ready():
    sprite.texture = load("res://icon.png")
    sprite.position = Vector2(100, 100)
    add_child(sprite)
    add_child(tween_up)
    add_child(tween_down)
 
func _process(delta):
    var up = Input.is_action_just_pressed("ui_up")
    var down = Input.is_action_just_pressed("ui_down")
    if (up or down) and (tween_up.is_active() or tween_down.is_active()):
        print('Please wait ...')
    elif up:
        print('up')
        tween_up.interpolate_property(sprite, 'scale',
            Vector2(1.0, 1.0), Vector2(2.0, 2.0), 2,
            Tween.TRANS_CIRC, Tween.EASE_OUT)
        tween_up.interpolate_property(sprite, 'scale', 
            Vector2(2.0, 2.0), Vector2(1.0, 1.0), 2,
            Tween.TRANS_QUAD, Tween.EASE_IN, 2)
        tween_up.start()
    elif down:
        print('down')
        tween_down.interpolate_property(sprite, 'scale',
            Vector2(1.0, 1.0), Vector2(0.5, 0.5), 2,
            Tween.TRANS_CUBIC, Tween.EASE_OUT)
        tween_down.interpolate_property(sprite, 'scale',
            Vector2(0.5, 0.5), Vector2(1.0, 1.0), 2,
            Tween.TRANS_QUAD, Tween.EASE_IN, 2)
        tween_down.start()


We can refactor a lot of code into separate functions for ease of reading and developing bug-free code. It will not be a good idea to put everything in the main loop, _process, but we should put them in separate functions.


The output during a scale up animation, with the project window size of 200 by 200:


Wednesday, March 27, 2019

14. Tilemap

We use this tileset from the website OpenGameArt.



This is the png image where each tile is 16 by 16 with spacing of 1 between them.



We have a scene with Node and a Sprite child. Set the texture of Sprite with the above image. Make sure to load image with 2D pixel preset using the Import tab next to Scene. Then we attach a script to Node which will generate our tile resource (tres) file. We can think of this as the atlas of all tiles that Godot can understand.



extends Node

var tile_size = Vector2(16, 16)
onready var texture = $Sprite.texture

func _ready():
    var col = 7
    var row = 5
    var ts = TileSet.new()
    for i in range(col):
        for j in range(row):
            var region = Rect2(i * tile_size.x + i, j * tile_size.y + j,
                               tile_size.x, tile_size.y)
            var id = i + j * 7
            ts.create_tile(id)
            ts.tile_set_texture(id, texture)
            ts.tile_set_region(id, region)
        ResourceSaver.save("res://tiles.tres", ts)



Even though we get 35 tiles, 10 are empty. After creating the tiles.tres file by running this scene, we can now test it. In a new scene, will will have the node of TileMap.


Set the cell size to 16 by 16, and resource to the tiles.tres file. Then we can paint the tiles. It is best to have a grid of 16 by 16 with snapping.


13. look_at

We have a Sprite with attached script for controlling position and rotation.


The position is controlled with left and right arrows, and rotation is set by the look_at() function. For the look_at function, we can imagine a vector going from sprite center (assuming no offset) to look at point. Here we use a look at point at top of screen. (In project setting we get game width and height both as 400). This is the attached script:



extends Sprite

const TOP = Vector2(200,0)
var old_rot

func _ready():
 position = Vector2(200,200)
 old_rot = int(rotation_degrees)

func _process(delta):
 if Input.is_action_pressed('ui_left'):
  position.x -= 1
 elif Input.is_action_pressed('ui_right'):
  position.x += 1
 look_at(TOP)
 if int(rotation_degrees) != old_rot:
  print("Rotation = ", int(rotation_degrees))
  old_rot = int(rotation_degrees)



The output, when we have moved the icon to left of screen so it keeps pointing to middle-top:


Thursday, March 21, 2019

12. Event-Driven Input

Under a Node, we have a Label child. Instead of polling in _process for particular input actions, we can use _input to handle input events.


We attach a script to Node, which outputs a string to the label if key is pressed. Had we attached the script to Label, we would either have referred to label.rect_position as self.rect_position or just rect_position. However I believe it more clear to write label.rect_position and attach script to parent.



extends Node

var label
var string

func _ready():
    label = get_node("Label")
    label.rect_position = Vector2(50, 50)

func _input(event):
    if event is InputEventKey:
        string = "Is key pressed: " + str(event.pressed) + "\n"
        string = string + "key: " + str(event.scancode)
        label.set_text(string)

The output, with window size of 200,200, when the spacebar is pressed and held down:


11. Random

Under a Node, we attach a script and using code create a Label and Timer.


The 'timeout' signal is emitted every 2 second and new random numbers are printed by this script.



extends Node

export (int) var max_int = 10
var label = Label.new()
var timer = Timer.new()
var rand_val

func _ready():
    randomize()
    add_child(label)
    add_child(timer)
    rand_val = randi() % max_int
    timer.connect("timeout",self,"_on_Timer_timeout")
    var rand_float = "%.2f" % randf()
    label.set_text("Random Integer: " + str(rand_val) +
        "\nRandom Float: " + rand_float )
    label.set_align(Label.ALIGN_CENTER)
    label.set_valign(Label.VALIGN_CENTER)
    label.margin_bottom = 200
    label.margin_right = 200
    timer.wait_time = 2
    timer.start()
 
 
func _on_Timer_timeout():
    rand_val = randi() % max_int
    var rand_float = "%.2f" % randf()
    label.set_text("Random Integer: " + str(rand_val) +
        "\nRandom Float: " + rand_float)


The output, with window size of 200,200, after a few seconds:


Wednesday, March 20, 2019

10. Timer

Under a Node, we have 2 childs: Label and Timer.


We attach a script to Node, which operates at a rate of 0.2 Hz (1/5 sec), that is, emits a 'timeout' signal every 1/5 second.



extends Node

var label
var timer
var count = 0

func _ready():
    label = get_node("Label")
    timer = get_node("Timer")
    timer.connect("timeout",self,"_on_Timer_timeout")
    label.set_text("Count: " + str(count))
    label.set_align(Label.ALIGN_CENTER)
    label.set_valign(Label.VALIGN_CENTER)
    label.rect_scale = Vector2(2,2)
    label.margin_bottom = 100
    label.margin_right = 100
    timer.wait_time = 5
    timer.start()
 
func _on_Timer_timeout():
    count += 1
    label.set_text("Count: " + str(count))

The output, with window size of 200,200, when the count has counted upto 5:


9. TextureButton

We use a CenterContainer to center a VBoxContainer, with 2 childs, TextureButton and Label. For the Label, in the Inspector, we set Align and VAlign as center. We could also have done this with code. However most of the time we will want to use set its properties graphically in the Inspector. This will be faster and you could see the results as well. We have to set margins so it is covers the entire window which we set as 500 by 200 in project settings. For the vbox, set the separator as 50.


These are the nodes used with the names used.



For the TextureButton, we use three images: normal, hover, and pressed. We generate them using GIMP program from File -> Create -> Buttons -> Round Button.


Normal:


Hover:


Pressed:


We attach this script to the Control node where we test for up and down signals emitted by the button.



extends Control

var button
var label

func _ready():
    var button_string = "center/vbox/button"
    var label_string = "center/vbox/label"
    button = get_node(button_string)
    button.texture_normal = load("res://button_normal.png")
    button.texture_pressed = load("res://button_pressed.png")
    button.texture_hover = load("res://button_hover.png")
    button.connect("button_down", self, "_on_TextureButton_button_down")
    button.connect("button_up", self, "_on_TextureButton_button_up")
    label = get_node(label_string)
    label.set_text("Waiting...")

func _on_TextureButton_button_down():
    label.set_text("Down state")

func _on_TextureButton_button_up():
    label.set_text("Up state")

The output, with window size of 500,200 and button pressed:


8. Button

A Button node is used in this example. We monitor two of the signals that a button can emit, for the down (pressed) and up (released) case.


Depending on which signal is emitted, we set the label to two different strings indicating the state.


We use the following nodes:



In the top-most node, Control, we attach this script:



extends Control

var label
var button

func _ready():
    button = get_node("Button")
    button.connect("button_up",self,"_on_Button_button_up")
    button.connect("button_down",self,"_on_Button_button_down")
    label = get_node("Label")
    button.set_text("Press Me")

func _on_Button_button_down():
    label.set_text("Button is down")

func _on_Button_button_up():
    label.set_text("Button is up")

The output, with window size of 200,200, when the button is pressed showing the button darkened and text indicating the state:


7. Position2D node

Position2D can be used to set reference points. We attach 2 reference points to a sprite (one offset north west and the other south east). The sprite (icon.png) is 64 by 64. Thus, the offsets set in the inspector are (-32,-32) and (32,32).


Like everything in programming there are many ways of doing the same thing. The advantage of this node is that it is seen as a crosshair in the editor. With Godot, you should be familiar of changing things both graphically and with code.


We use the following nodes:



In the top-most node, Player (an Area2D node), we attach this script, which prints the two reference points as we move the sprite:



extends Area2D

var sprite
var pos
var pos_nw
var pos_se

func _ready():
    sprite = get_node("sprite")
    sprite.position = Vector2(100, 100)
    pos_nw = sprite.get_node("pos_nw")
    pos_se = sprite.get_node("pos_se")
    var pos_nw_loc = pos_nw.get_global_position()
    var pos_se_loc = pos_se.get_global_position()
    print("Initial pos_nw_loc = " + str(pos_nw_loc))
    print("Initial pos_se_loc = " + str(pos_se_loc))

func _process(delta):
    var change = 0
    pos = Vector2(0, 0)
    if Input.is_action_pressed('ui_left'):
        pos.x = -1
        change = 1
    elif Input.is_action_pressed('ui_right'):
        pos.x = 1
        change = 1
    elif Input.is_action_pressed('ui_up'):
        pos.y = -1
        change = 1
    elif Input.is_action_pressed('ui_down'):
        pos.y = 1
        change = 1
    if change == 1:
        var pos_nw_loc = pos_nw.get_global_position()
        var pos_se_loc = pos_se.get_global_position()
        print("pos_nw_loc = " + str(pos_nw_loc))
        print("pos_se_loc = " + str(pos_se_loc))
    sprite.position += pos

The output, with window size of 200,200:


6. TextureProgress

We create a MarginContainer node (with margins left and right 20, and margins top and bottom 75). We have to set the anchors for bottom and right to 1 so it starts measuring from bottom-right for these two margins.


Then, as the MaginContainer's child, we create a TextureProgress. The under, progress, and over images are shown here:


Under: (Background).


Progress: (this will be clipped by Range.value).


Over: (every pixel is transparent except 4 corners are white - click to verify).




We attach this script to Node:


extends Node

const SPEED = 50
var mc = MarginContainer.new()
var tp

func _ready():
    VisualServer.set_default_clear_color(ColorN("white"))
    mc.anchor_bottom = 1
    mc.anchor_right = 1
    mc.margin_left = 20
    mc.margin_right = 20
    mc.margin_top = 75
    mc.margin_bottom = 75
    tp = TextureProgress.new()
    tp.texture_progress = load("res://progress.png")
    tp.texture_over = load("res://over.png")
    tp.texture_under = load("res://under.png")
    mc.add_child(tp)
    add_child(mc)
 
func _process(delta):
    tp.value = fmod(tp.value + SPEED * delta, 100)

The output, with window size of 200,200 and clear color of white, when the progress is about half:


Tuesday, March 19, 2019

5. Input

For the Sprite in Example 4, we use Input to keep checking whether we are pressing an arrow key.


If so, we move in that direction by changing the sprite.position member variable, which it inherits from Node2D.


The screen size, is 200 by 200 and sprite initial position is the center (100, 100).


We have to do the movement code in the game loop in the _process function.



extends Node

var pos
var sprite = Sprite.new()

func _ready():
    sprite.texture = load("res://icon.png")
    sprite.position = Vector2(100, 100)
    add_child(sprite)

func _process(delta):
    pos = Vector2(0,0)
    if Input.is_action_pressed('ui_left'):
        pos.x = -1
    elif Input.is_action_pressed('ui_right'):
        pos.x = 1
    elif Input.is_action_pressed('ui_up'):
        pos.y = -1
    elif Input.is_action_pressed('ui_down'):
        pos.y = 1
    sprite.position += pos 

The output after the sprite is moved to top left:


4. Sprite

We attach a script to Node, where we create a Sprite node (using Sprite.new()).


The texture for the sprite is selected as the Godot icon.


The screen size, was set as 200 by 200 in project settings. We set the position as the center (100, 100). Note position is a member variable of Node2D and not Sprite class.


We set the rotation as PI/4 radians (or 45 degrees). We print out the angle using Godot's rad2deg (radian to degree) function.


We finally have to attach the Sprite node to the Node. We may use self or not, to refer to the Node, thus we could also have used add_child(), rather than self.add_child().



extends Node

var sprite = Sprite.new()

func _ready():
    sprite.texture = load("res://icon.png")
    sprite.position = Vector2(100, 100)
    sprite.rotation = PI / 4
    print("I am ", self)
    self.add_child(sprite)
    print("rotation = ", rad2deg(sprite.rotation))

The output shows the rotated image:


3. Using MarginContainer, VBoxContainer, Label and TextureRect nodes

We use several nodes, created by code, to create a simple GUI. Our window size is 200 by 200, set in the Project Settings. First we set the background color as lime.


Using a Node with attached script, we first create a MarginContainer, then set its anchors and margins so there is 50px margin on all four sides.


We then have a VBoxContainer as its child. We set the separation as 25, so to have some spacing between the 2 child nodes.


The child nodes are Label (text of Godot 3.1 with color red), and TextureRect, with the image of the Godot icon.


This is the script


extends Node

var mc = MarginContainer.new()

func _ready():
    VisualServer.set_default_clear_color(ColorN("lime"))
    mc.anchor_right = 1
    mc.anchor_bottom = 1
    mc.margin_left = 50
    mc.margin_top = 50
    mc.margin_right = -50
    mc.margin_bottom = -50
    var vbox = VBoxContainer.new()
    vbox.add_constant_override("separation",25)
    mc.add_child(vbox)
    var lbl = Label.new()
    lbl.text = "Godot 3.1"
    lbl.add_color_override("font_color", Color(1,0,0,1))
    vbox.add_child(lbl)
    var tr = TextureRect.new()
    tr.texture = load("res://icon.png")
    vbox.add_child(tr)
    add_child(mc)

This is the output:


2. RichTextLabel

We create a RichTextLabel node using code and set its anchor, margin and text.


We set the text as Hello World!!! and underline the letters.


In project settings, we set window size as 200 by 50.


This script is attached to Node


extends Node

var rtl = RichTextLabel.new()

func _ready():
    rtl.anchor_right = 1
    rtl.anchor_bottom = 1
    rtl.margin_left = 50
    rtl.margin_top = 15
    rtl.margin_right = -50
    rtl.margin_bottom = -15
    rtl.bbcode_enabled = true
    rtl.bbcode_text = "[u]Hello world[/u]!!!"
    add_child(rtl)

The output is a label with the text 'Hello World!!!':


1. Label

We create a Node and attach a script to it.


The script creates a Label node with code, and sets some of its properties, and finally adds it as a child.


We use the screen size, which we set to 200 by 50 in project settings.



extends Node

var lbl = Label.new()
var screen_size

func _ready():
    screen_size = get_viewport().size
    lbl.set_size(screen_size)
    lbl.set_align(Label.ALIGN_CENTER)
    lbl.set_valign(Label.VALIGN_CENTER)
    lbl.set_text("Hello world!")
    add_child(lbl)

The output is a label with the text 'Hello World!':