Skip to content

Commit

Permalink
Update Godot 101 tour to Godot 4.3
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanLovato committed Sep 20, 2024
1 parent 2537e61 commit 9641703
Show file tree
Hide file tree
Showing 1,936 changed files with 37,716 additions and 3,765 deletions.
27 changes: 25 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
MIT License
We use two licenses for this project:

Copyright (c) 2023 GDQuest Demos
- The source code (script files, scene files, shaders, and generally all Godot-generated resources) is available under the MIT license.
- Art assets (image textures and 3D models) are [CC-By 4.0](https://creativecommons.org/licenses/by/4.0/). In short, if you reuse art from the project, you can add this to your credits: `Additional assets CC-By 4.0 GDQuest (https://www.gdquest.com/)`.

You can find the details for each license below.

## MIT License

Copyright (c) 2024 GDQuest

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -19,3 +26,19 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

## CC-By 4.0 License

You are free to:

- Share — copy and redistribute the material in any medium or format for any purpose, even commercially.
- Adapt — remix, transform, and build upon the material for any purpose, even commercially.

Under the following terms:

1. Attribution — You must give appropriate credit , provide a link to the license, and indicate if changes were made . You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.

The licensor cannot revoke these freedoms as long as you follow the license terms.

Read the full Creative Commons Attribution 4.0 International Public License: https://creativecommons.org/licenses/by/4.0/legalcode
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Godot Tours: 101 - The Godot Editor

*Updated to Godot 4.3*

A free and open-source tour using the [Godot Tours](https://github.com/GDQuest/godot-tours/) add-on.

If you're new to gamedev and you want to download the tour and run it, head over to the [GDQuest website](http://gdquest.com/tutorial/godot/learning-paths/godot-tours-101/) for detailed instructions.

**Minimum required Godot version: Godot 4.2 standard** (*not* the .NET edition)
**Minimum required Godot version: Godot 4.3 standard** (*not* the .NET edition)

![Screenshot of one of the first steps of the tour, showing the running game and a bubble inviting you to run the game.](readme/tour-101-screenshot-01.webp)

Expand Down Expand Up @@ -43,4 +45,3 @@ Given our limited resources and the work this project represents, it is provided
Except for bug fixes, we will generally not accept contributions to this tour. If you'd like to create a different tour based on this one, please feel free to fork the project.

Note that any code change or bug related to the `godot_tours` add-on itself should be submitted to the [Godot Tours repository](https://github.com/GDQuest/godot-tours/) instead.

41 changes: 41 additions & 0 deletions addons/gdquest_sparkly_bag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# GDQuest Sparkly Bag Utils

Is a collection of utilities dealing with repeating patterns that we discovered in time.

They're not necessarily related to each other, and some are generic while others are very specific.

The collection includes:

- A post import script for GLTF resources that cleans up any inconsistencies with naming conventions and adds support for `AnimatableBody3D` convention via the `-anim` suffix.
- A utility library called `SparklyBagUtils`.

## ✗ WARNING

> Compatible: Godot `>= v4.0`
## ✓ Install

### Manual

1. Copy the contents of this folder into `res://addons/gdquest_sparkly_bag/`.
1. Profit.

### gd-plug

1. Install **gd-plug** using the Godot Asset Library.
1. Save the following code into the file `res://plug.gd` (create the file if necessary):

```gdscript
#!/usr/bin/env -S godot --headless --script
extends "res://addons/gd-plug/plug.gd"
func _plugging() -> void:
plug(
"git@github.com:GDQuest/godot-addons.git",
{include = ["addons/gdquest_sparkly_bag"]}
)
```

1. On Linux, make the `res://plug.gd` script executable with `chmod +x plug.gd`.
1. Using the command line, run `./plug.gd install` or `godot --headless --script plug.gd install`.
22 changes: 22 additions & 0 deletions addons/gdquest_sparkly_bag/autoloads/reset_net_injector.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
extends Node

var reset_net_y_global_position := -10.0


func _ready() -> void:
var scene := get_tree().current_scene
if scene is Node3D:
var reset_net := Area3D.new()
reset_net.area_entered.connect(_on_node_entered)
reset_net.body_entered.connect(_on_node_entered)

var collision_shape := CollisionShape3D.new()
collision_shape.shape = WorldBoundaryShape3D.new()
reset_net.add_child(collision_shape)
scene.add_child(reset_net)
reset_net.global_position.y = reset_net_y_global_position


func _on_node_entered(node: Node3D) -> void:
if node.has_method("reset"):
node.reset()
75 changes: 75 additions & 0 deletions addons/gdquest_sparkly_bag/scene_post_import.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@tool
extends EditorScenePostImport

const Utils := preload("sparkly_bag_utils.gd")

const SUFFIXES = ["-anim", "-col"]
const AABB_SIZE := "aabb_size"


func _post_import(scene: Node) -> Object:
for node in scene.find_children("*"):
if node.name.ends_with("-anim") and node is MeshInstance3D:
node.create_convex_collision()
var animatable_body := AnimatableBody3D.new()
animatable_body.name = "AnimatableBody3D"
animatable_body.sync_to_physics = false
for child in node.get_children():
# BUG: where the collision shape doesn't get transformed if `sync_to_physics = true`
child.replace_by(animatable_body)
child.free()
node.name = node.name.replace("-anim", "")

elif node.name.ends_with("-rigid"):
node.name = node.name.replace("-rigid", "")

elif node is AnimationPlayer:
for animation_name in node.get_animation_list():
var animation_library: AnimationLibrary = node.get_animation_library("")
var animation: Animation = node.get_animation(animation_name)
if animation_name.ends_with("-noimp"):
animation_library.remove_animation(animation_name)
continue

for track_index in range(animation.get_track_count()):
var path := animation.track_get_path(track_index)
var clean_path := ""
for name_index in range(path.get_name_count()):
for suffix in SUFFIXES:
var path_name := path.get_name(name_index)
if path_name.ends_with(suffix):
clean_path = clean_path.path_join(path_name.replace(suffix, ""))
break

if not clean_path.is_empty():
animation.track_set_path(track_index, clean_path)

if node is MeshInstance3D:
var aabb: AABB = node.mesh.get_aabb()
for index in range(node.mesh.get_surface_count()):
var material_file_name: StringName = (
"%s.tres" % node.mesh.get("surface_%d/name" % index).to_snake_case()
)
var found := Utils.fs_find(material_file_name)
if found.return_code != Utils.ReturnCode.OK:
return scene

if found.is_empty():
var message := (
"[ScenePostImport:WARN] Missing material `%s` for `%s`"
% [material_file_name, node.name]
)
print(message)
else:
for path: String in found.result:
var material := load(path)
if material is ShaderMaterial:
material.set_shader_parameter(AABB_SIZE, aabb.size)
node.mesh.surface_set_material(index, material)
var message := (
"[ScenePostImport:INFO] Material found @ `%s` for `%s`"
% [path, node.name]
)
print(message)
break
return scene
32 changes: 32 additions & 0 deletions addons/gdquest_sparkly_bag/sparkly_bag_coroutine.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Inner:
signal finished

var _count := 0

func _init(count: int) -> void:
_count = count

func check() -> void:
_count -= 1
if _count == 0:
finished.emit()


static func parallel(coroutines: Array[Callable]) -> Array:
var results := []
var inner := Inner.new(coroutines.size())
for coroutine in coroutines.map(
func(coroutine: Callable) -> Callable: return func() -> void:
results.push_back(await coroutine.call())
inner.check()
):
coroutine.call()
await inner.finished
return results


static func sequence(coroutines: Array[Callable]) -> Array:
var result := []
for coroutine in coroutines:
result.push_back(await coroutine.call())
return result
141 changes: 141 additions & 0 deletions addons/gdquest_sparkly_bag/sparkly_bag_utils.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const SEP := "/"

enum ReturnCode { OK, FAIL, WARN, SKIP, EXE_NOT_FOUND = 127, CODE_NOT_FOUND }


class Result:
var return_code := ReturnCode.OK
var result: Variant = null

func _init(result: Variant = null) -> void:
self.result = result


static func fs_find(pattern: String = "*", path: String = "res://", do_include_hidden := true, do_fail := true) -> Result:
const TAG := { ReturnCode.FAIL: "FAIL", ReturnCode.SKIP: "SKIP" }

var result: Result = Result.new([])
var is_file := not pattern.ends_with(SEP)
pattern = pattern.rstrip(SEP)

var dir := DirAccess.open(path)
dir.include_hidden = do_include_hidden

if DirAccess.get_open_error() != OK:
result.return_code = ReturnCode.FAIL if do_fail else ReturnCode.SKIP
printerr("%s: could not open [%s]" % [TAG[result.return_code], path])
return result

if dir.list_dir_begin() != OK:
result.return_code = ReturnCode.FAIL if do_fail else ReturnCode.SKIP
printerr("%s: could not list contents of [%s]" % [TAG[result.return_code], path])
return result

path = dir.get_next()
while path.is_valid_filename():
var new_path: String = dir.get_current_dir().path_join(path)
if dir.current_is_dir():
if not is_file and (path == pattern or new_path.match(pattern)):
result.result.push_back(new_path)
result.result += fs_find(pattern, new_path).result
elif path == pattern or new_path.match(pattern):
result.result.push_back(new_path)
path = dir.get_next()
return result


static func fs_remove_dir(base_path: String) -> void:
if not DirAccess.dir_exists_absolute(base_path):
return

var found := fs_find("*", base_path)
for path: String in found.result:
DirAccess.remove_absolute(path)

found = fs_find("*/", base_path)
found.result.reverse()
for path in found.result:
DirAccess.remove_absolute(path)
DirAccess.remove_absolute(base_path)


static func fs_copy_dir(from_path: String, to_path: String, ignore: Array[String] = []) -> ReturnCode:
var dir := DirAccess.open(from_path)
from_path = dir.get_current_dir()
var from_base_path := from_path.get_base_dir()
var found := fs_find("*", from_path)
if found.return_code != ReturnCode.OK:
return found.return_code

for file_path: String in found.result:
if ignore.any(func(p: String) -> bool: return file_path.match(p)):
continue
var destination_file_path := file_path.replace(from_base_path, to_path)
var destination_dir_path := destination_file_path.get_base_dir()
DirAccess.make_dir_recursive_absolute(destination_dir_path)
DirAccess.copy_absolute(file_path, destination_file_path)
return found.return_code


static func os_execute(exe: String, args: Array, do_read_stderr := true) -> ReturnCode:
var output := []
var return_code := OS.execute(exe, args, output, do_read_stderr)
for line in output:
print_rich(line)

var is_fail := (
not return_code in ReturnCode.values()
or output.any(func(s: String) -> bool: return "FAIL" in s)
)
return ReturnCode.FAIL if is_fail else (return_code as ReturnCode)


static func os_parse_user_args(help_description := [], supported_args := []) -> Result:
var result := Result.new({args = {}})

const ARG_HELP := ["-h", "--help", "Show this help message."]
supported_args = [ARG_HELP] + supported_args

var is_arg_predicate := func(s: String) -> bool: return s.begins_with("-")
var arg_to_help := func(a: Array) -> String:
var args := a.filter(is_arg_predicate)
var help_message := a.filter(func(s: String) -> bool: return not s.begins_with("-"))
return " %s" % "\n\t".join([" ".join(args), "".join(help_message)])
var help_message := help_description + ["Arguments"] + supported_args.map(arg_to_help)
result.result.help_message = "\n".join(help_message)

result.result.user_args = OS.get_cmdline_user_args()
if not ARG_HELP.filter(func(a: String) -> bool: return a in result.result.user_args).is_empty():
print_rich(result.result.help_message)
return result

var flat_supported_args := flatten(supported_args).filter(is_arg_predicate)
var unknown_args: Array[String] = []
for arg: String in result.result.user_args:
var parts := arg.split("=")
var key := parts[0]
if key in flat_supported_args:
result.result.args[key] = parts[1] if parts.size() == 2 else null
else:
unknown_args.push_back(key)

if not unknown_args.is_empty():
var message := [
"Unknown command-line arguments %s." % str(unknown_args),
"Supported arguments %s." % str(flat_supported_args),
]
push_warning(" ".join(message))
result.return_code = ReturnCode.WARN
return result


static func flatten_unique(array: Array) -> Array:
var result := {}
for key in flatten(array):
result[key] = null
return result.keys()


static func flatten(array: Array) -> Array:
return array.reduce(func(acc: Array, xs: Array) -> Array: return acc + xs, [])

Binary file not shown.
Loading

0 comments on commit 9641703

Please sign in to comment.