initial project and basic controller
This commit is contained in:
commit
4d6af3d098
|
@ -0,0 +1,15 @@
|
|||
* text=auto eol=lf
|
||||
*.glb filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.material filter=lfs diff=lfs merge=lfs -text
|
||||
*.mdd filter=lfs diff=lfs merge=lfs -text
|
||||
*.abc filter=lfs diff=lfs merge=lfs -text
|
||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
*.blend filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.pur filter=lfs diff=lfs merge=lfs -text
|
||||
*.res filter=lfs diff=lfs merge=lfs -text
|
|
@ -0,0 +1,7 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
# Blender Ignores
|
||||
*.blend1
|
||||
__pycache__/**/*
|
||||
*.pyc
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Anatol Bogun
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
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.
|
|
@ -0,0 +1,184 @@
|
|||
# godot-smoother-node
|
||||
|
||||
A **Godot 4** node type that smoothes scene node movements by interpolating `_physics_process` steps.
|
||||
|
||||
![godot-smoother-node-comparison](https://user-images.githubusercontent.com/7110246/209624079-86824089-444d-4f6e-bd02-b2b38e3952c4.gif)
|
||||
|
||||
Above: Not smoothed vs. smoothed.
|
||||
|
||||
## *Smoother* node
|
||||
|
||||
This node interpolates properties of other nodes between their `_physics_process`es. The interpolation is applied in the `_process` loop which ensures that nodes move smoothly, even if the `_physics_process` is called less often than the games fps rate which is typically synced to the current screen's refresh rate.
|
||||
|
||||
By default only the node `position` is interpolated.
|
||||
|
||||
Visit [godot-smoother-node-test-scene](https://github.com/anatolbogun/godot-smoother-node-test-scene) to download a Godot 4 sample project.
|
||||
|
||||
### YouTube Tutorial
|
||||
|
||||
[![Youtube Tutorial](https://user-images.githubusercontent.com/7110246/209792804-f471d454-2d0a-487f-8599-46ef0af0ea5e.png)](https://www.youtube.com/watch?v=jIkPYlNF50Q)
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
Add the [smoother.gd](https://github.com/anatolbogun/godot-smoother-node/blob/main/smoother.gd) script to your project. Since it has a `class_name` it is automatically added to the available nodes of the project.
|
||||
|
||||
Simply add the *Smoother* node as a child to your root node (like a level root). By default it will interpolate the `position` of all supported[^1] and relevant[^2] nodes in the scene.
|
||||
|
||||
![godot-smoother-child-of-root-node](https://user-images.githubusercontent.com/7110246/209628202-6339f715-21fc-4529-b42d-a778f871a532.png)
|
||||
|
||||
[^1]: Currently `RigidBody2D` and `RigidBody3D` are not supported.
|
||||
[^2]: Nodes that have no custom `_physics_process` are automatically ignored. So are target properties that a node may not have.
|
||||
|
||||
#### Properties
|
||||
|
||||
![godot-smoother-default-options](https://user-images.githubusercontent.com/7110246/209629766-595b34f9-309a-453c-bf52-440b2f4940de.png)
|
||||
|
||||
*Smoother* default options are:
|
||||
- **properties**:`Array[String]` = `["position"]`[^3] — The listed properties are interpolated[^4] unless a node does not have the property in which case it will be ignored for that particular node.
|
||||
- **smooth_parent**:`bool` = `true` — Include the parent node for interpolation.
|
||||
- **recursive**:`bool` = `true` — Include recursive children. Note that recursive is **relative to the *Smoother*'s parent**. In a way the *Smoother* node attaches to a parent and takes the parent as the base for its operations.[^5]
|
||||
- **includes**:`Array[NodePath]` = `[]` — Any node listed in this array will be smoothed unless listed in `excludes`.
|
||||
- **excludes**:`Array[NodePath]` = `[]` — Any node listed in this array will be excluded from smoothing. This overwrites any options above.
|
||||
|
||||
[^3]: Note that at the moment of writing, Godot does not display the default `["position"]` value for properties in the inspector, even though the value applies. This may be fixed in a future Godot release.
|
||||
[^4]: Interpolation only works properties of data types that are supported by [lerp](https://docs.godotengine.org/en/latest/classes/class_@globalscope.html#class-globalscope-method-lerp), i.e. `int`, `float`, `Vector2`, `Vector3`, `Vector4`, `Color`, `Quaternion`, `Basis`
|
||||
[^5]: The *Smoother* node cannot access nodes above its parent node, it can only act on its parent, parent's children or parent's nested children, except a node higher in the tree hierarchy is an item in `includes`.
|
||||
|
||||
Adding other properties that the *Smoother* will attempt to interpolate is as easy as adding the property name strings.
|
||||
|
||||
![godot-smoother-options-properties](https://user-images.githubusercontent.com/7110246/209642811-3b268660-c9d9-4679-9e8b-ec1ee9c0a6b9.png)
|
||||
|
||||
#### Performance Optimisations
|
||||
|
||||
For large levels you may want to optimise things (as you probably should regardless of using the Smoother node). A good approach would be to use the `VisibleOnScreenNotifier2D`/`VisibleOnScreenNotifier3D` and use their `screen_entered` and `screen_exited` signals to update the `includes` or `excludes` array.
|
||||
|
||||
##### Method 1: excludes
|
||||
|
||||
Add all *off-screen* moveable nodes to `excludes` and remove them when they come *on-screen*, e.g.
|
||||
``` gdscript
|
||||
func _on_node_screen_entered(node:Node) -> void:
|
||||
$Smoother.add_exclude_node(node)
|
||||
|
||||
func _on_node_screen_exited(node:Node) -> void:
|
||||
$Smoother.add_exclude_node(node)
|
||||
```
|
||||
Since excludes overwrite all other *Smoother* settings this is the most flexible option.
|
||||
|
||||
One caveat is that on entering the tree, the `VisibleOnScreenNotifier2D`/`VisibleOnScreenNotifier3D` do not fire the `screen_exited` signal, so you may have to emit this in a Node's `_enter_tree`, e.g.
|
||||
``` gdscript
|
||||
func _enter_tree() -> void:
|
||||
if !$VisibleOnScreenNotifier2D.is_on_screen():
|
||||
_on_screen_exited()
|
||||
```
|
||||
|
||||
##### Method 2: includes
|
||||
|
||||
Add all *on-screen* moveable nodes to `includes` and remove them when they come *off-screen*, e.g.
|
||||
``` gdscript
|
||||
func _on_node_screen_entered(node:Node) -> void:
|
||||
$Smoother.add_include_node(node)
|
||||
|
||||
func _on_node_screen_exited(node:Node) -> void:
|
||||
$Smoother.remove_include_node(node)
|
||||
```
|
||||
Since includes adds nodes but does not interfere with other options you probably should set the `smooth_parent` and `recursive` options to `false`.
|
||||
|
||||
On entering the tree, the `VisibleOnScreenNotifier2D`/`VisibleOnScreenNotifier3D` automatically fire the `screen_entered` signal, so nothing needs to be done.
|
||||
|
||||
##### For Both Methods
|
||||
|
||||
Either way it's probably a good idea to emit the `screen_exited` signal on `_exit_tree` to cleanup the `inludes` or `excludes` array, e.g.
|
||||
``` gdscript
|
||||
func _exit_tree() -> void:
|
||||
emit_signal("screen_exited", self)
|
||||
```
|
||||
|
||||
The [godot-smoother-node-test-scene](https://github.com/anatolbogun/godot-smoother-node-test-scene) uses performance optimisations in [level2d.gd](https://github.com/anatolbogun/godot-smoother-node-test-scene/blob/main/src/Levels/level2d.gd#L18-L37) and some sprite nodes that emit signals as mentioned above.
|
||||
|
||||
##### Debugging
|
||||
|
||||
You can always check the currently smoothed nodes to see if your performance optimisation works as intended, e.g.
|
||||
``` gdscript
|
||||
print("smoothed nodes: ", $Smoother.smoothed_nodes.map(func (node:Node): return node.name))
|
||||
```
|
||||
The above code displays the currently smoothed nodes in the Godot debugger when the `includes` or `excludes` array is updated:
|
||||
![godot-smoother-debugging-smoothed-nodes](https://user-images.githubusercontent.com/7110246/209639351-97a37452-bbfd-494a-8c7e-da4248776b99.png)
|
||||
|
||||
#### Teleporting
|
||||
|
||||
When teleporting a node (changing the position) you may want to call `reset_node(node)` or `reset_node_path(path)`, otherwise a teleport may not work as expected, e.g.
|
||||
``` gdscript
|
||||
func _on_node_teleport_started(node: Node) -> void:
|
||||
$Smoother.reset_node(node)
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
#### Collision Detection
|
||||
|
||||
Collision detection still happens in the `_physics_process`, so if the `physics_ticks_per_second` value in the project settings is too low you may experience seemingly incorrect or punishing collision detection. The default 60 `physics_ticks_per_second` should a good choice. To test this node you may want to temporarily reduce physics ticks to a lower value and toggle this node's process mode on and off. The [godot-smoother-node-test-scene](https://github.com/anatolbogun/godot-smoother-node-test-scene) sample project has only 13 `physics_ticks_per_second` for demonstration purposes (not recommended for a "real" project). As a result collision detection is quite inaccurate.
|
||||
|
||||
#### Always the First Child
|
||||
|
||||
The code will automatically keep the *Smoother* node as the first child of its parent node because its `_physics_process` and `_process` code *must* run before nodes that are interpolated by it.
|
||||
|
||||
#### Process Priority
|
||||
|
||||
When `smooth_parent` is enabled the `process_priority` will be kept at a lower value than the parent's, i.e. it will be processed earlier, again because the *Smoother*'s `_physics_process` and `_process` code *must* run before nodes that are interpolated by it.
|
||||
|
||||
#### Data Structure
|
||||
The core of this class is the `_properties` dictionary which holds `_physics_process` origin and target values of the relevant nodes and properties. These values are then interpolated in `_process`.
|
||||
|
||||
For easier understanding of the code, the structure is:
|
||||
``` gdscript
|
||||
_properties[node][property][0] # origin value of a node's property
|
||||
_properties[node][property][1] # target value of a node's property
|
||||
```
|
||||
So for example:
|
||||
``` gdscript
|
||||
_properties
|
||||
├── Player
|
||||
│ └── position
|
||||
│ ├── 0:Vector2 = {x: 0, y: 0} # origin
|
||||
│ └── 1:Vector2 = {x: 10, y: 20} # target
|
||||
│ └── rotation
|
||||
│ ├── 0:float = 0 # origin
|
||||
│ └── 1:float = 15 # target
|
||||
├── Enemy
|
||||
│ └── position
|
||||
│ ├── 0:Vector2 = {x: 100, y: 0} # origin
|
||||
│ └── 1:Vector2 = {x: 70, y: 0} # target
|
||||
│ └── rotation
|
||||
│ ├── 0:float = 0 # origin
|
||||
│ └── 1:float = -5 # target
|
||||
:
|
||||
etc.
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
#### RigidBody2D / RigidBody3D
|
||||
|
||||
Currently this class does not work with `RigidBody2D` or `RigidBody3D` nodes. Please check out https://github.com/lawnjelly/smoothing-addon/ which has a more complicated setup but rewards the effort with more precision and less limitations. Or help to make this code work with rigid bodies if it's possible at all.
|
||||
|
||||
#### One Step Behind
|
||||
|
||||
Interpolation is one `_physics_process` step behind because we need to know the origin and target values for an interpolation to occur, so in a typical scenario this means a delay of 1/60 second which is the default `physics_ticks_per_second` in the project settings.
|
||||
|
||||
#### No Look Ahead
|
||||
|
||||
Interpolation does not look ahead for collision detection. That means that for example if a sprite falls to hit the ground and the last `_physics_process` step before impact is very close to the ground, interpolation will still occur on all `_physics` frames between which may have a slight impact cushioning effect. However, with 60 physics fps this is hopefully negligible.
|
||||
|
||||
#### Godot 4
|
||||
|
||||
This class is written in GDScript 2 for Godot 4+, but feel free to get in touch and we can add a `godot-3` branch or fork the project and make adjustments. It's probably not too hard to backport since it only relies on other nodes' properties, the `position` property by default.
|
||||
|
||||
|
||||
### Support
|
||||
|
||||
I'm fairly new to Godot, so if you find any bugs or have suggestions for performance improvements in the *Smoother* code for example, please let me know.
|
||||
|
||||
I haven't tested this much yet, primarily only in the 2d and 3d test levels in the [godot-smoother-node-test-scene](https://github.com/anatolbogun/godot-smoother-node-test-scene).
|
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dyuji2reawgld"
|
||||
path="res://.godot/imported/icon.png-036e47a3038781269e79b77dd42621d5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/smoother/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-036e47a3038781269e79b77dd42621d5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
|
@ -0,0 +1,229 @@
|
|||
# MIT LICENSE
|
||||
#
|
||||
# Copyright 2022 Anatol Bogun
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or
|
||||
# substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE 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.
|
||||
|
||||
|
||||
class_name Smoother extends Node
|
||||
|
||||
## Smoother Node
|
||||
## Version: 1.0.4
|
||||
##
|
||||
## A node type that smoothes scene nodes' properties by interpolating _physics_process steps.
|
||||
##
|
||||
## For documentation please visit https://github.com/anatolbogun/godot-smoother-node .
|
||||
|
||||
## Node properties that are interpolated.
|
||||
## Defaults to ["position"], even if not displayed in the inspector.
|
||||
@export var properties:Array[String] = ["position"]
|
||||
|
||||
## Apply interpolation to this node's parent.
|
||||
@export var smooth_parent: = true :
|
||||
set (value):
|
||||
if value == false:
|
||||
# remove parent from _properties in case this gets toggled on and off during runtime
|
||||
_properties.erase(get_parent())
|
||||
|
||||
smooth_parent = value
|
||||
|
||||
## Apply interpolation to the recursive children of this node's parent.
|
||||
@export var recursive: = true
|
||||
|
||||
## Explicitly include node paths in addition to the nodes that are included by other Smoother
|
||||
## settings.
|
||||
@export var includes:Array[NodePath] = []
|
||||
|
||||
## Explicitly exclude node paths.
|
||||
## This will exclude nodes that would otherwise be included by other settings.
|
||||
@export var excludes:Array[NodePath] = []
|
||||
|
||||
# get an array of all currently smoothed nodes; mainly for debugging performance optimisations
|
||||
var smoothed_nodes:Array[Node] :
|
||||
get:
|
||||
var parent: = get_parent()
|
||||
return _get_physics_process_nodes(parent, !smooth_parent) if parent != null else [] as Array[Node]
|
||||
|
||||
var _properties: = {}
|
||||
var _physics_process_nodes:Array[Node]
|
||||
var _physics_process_just_updated: = false
|
||||
|
||||
|
||||
## Reset all smoothed nodes.
|
||||
func reset() -> void:
|
||||
_properties.clear()
|
||||
|
||||
|
||||
## Reset a specific node. You may want to call this when a node gets teleported.
|
||||
func reset_node(node:Node) -> void:
|
||||
_properties.erase(node)
|
||||
|
||||
|
||||
## Reset a specific Node by NodePath. You may want to call this when a Node gets teleported.
|
||||
func reset_node_path(path:NodePath) -> void:
|
||||
var node: = get_node_or_null(path)
|
||||
|
||||
if node != null:
|
||||
reset_node(node)
|
||||
|
||||
|
||||
## Add a Node to the includes Array[NodePath].
|
||||
func add_include_node(node:Node) -> Array[NodePath]:
|
||||
return add_include_path(get_path_to(node))
|
||||
|
||||
|
||||
## Add a NodePath to the includes Array[NodePath].
|
||||
func add_include_path(path:NodePath) -> Array[NodePath]:
|
||||
return _add_unique_to_array(includes, path) as Array[NodePath]
|
||||
|
||||
|
||||
## Remove a Node from the includes Array[NodePath].
|
||||
func remove_include_node(node:Node) -> Array[NodePath]:
|
||||
return remove_include_path(get_path_to(node))
|
||||
|
||||
|
||||
## Remove a NodePath from the includes Array[NodePath].
|
||||
func remove_include_path(path:NodePath) -> Array[NodePath]:
|
||||
return _remove_all_from_array(includes, path) as Array[NodePath]
|
||||
|
||||
|
||||
## Add a Node to the excludes Array[NodePath].
|
||||
func add_exclude_node(node:Node) -> Array[NodePath]:
|
||||
return add_exclude_path(get_path_to(node))
|
||||
|
||||
|
||||
## Add a NodePath to the excludes Array[NodePath].
|
||||
func add_exclude_path(path:NodePath) -> Array[NodePath]:
|
||||
return _add_unique_to_array(excludes, path) as Array[NodePath]
|
||||
|
||||
|
||||
## Remove a Node from the excludes Array[NodePath].
|
||||
func remove_exclude_node(node:Node) -> Array[NodePath]:
|
||||
return remove_exclude_path(get_path_to(node))
|
||||
|
||||
|
||||
## Remove a NodePath from the excludes Array[NodePath].
|
||||
func remove_exclude_path(path:NodePath) -> Array[NodePath]:
|
||||
return _remove_all_from_array(excludes, path) as Array[NodePath]
|
||||
|
||||
|
||||
## Add an item to an array unless the array already contains that item.
|
||||
func _add_unique_to_array(array:Array, item:Variant) -> Array:
|
||||
if !array.has(item):
|
||||
array.push_back(item)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
## Remove all array items that match item.
|
||||
func _remove_all_from_array(array:Array, item:Variant) -> Array:
|
||||
while array.has(item):
|
||||
array.erase(item)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
## Apply interpolation to all smoothed_nodes supported properties.
|
||||
func _process(_delta: float) -> void:
|
||||
for node in _physics_process_nodes:
|
||||
if !_properties.has(node): continue
|
||||
|
||||
for property in _properties[node]:
|
||||
var values = _properties[node][property]
|
||||
|
||||
if values.size() == 2:
|
||||
if _physics_process_just_updated:
|
||||
values[1] = node[property]
|
||||
|
||||
node[property] = lerp(values[0], values[1], Engine.get_physics_interpolation_fraction())
|
||||
|
||||
_physics_process_just_updated = false
|
||||
|
||||
|
||||
## Store all smoothed_nodes' relevant properties of the previous (origin) and this (target)
|
||||
## _physics_process frames for interpolation in the upcoming _process frames and apply the origin
|
||||
## values.
|
||||
func _physics_process(_delta: float) -> void:
|
||||
var parent: = get_parent()
|
||||
if parent == null: return
|
||||
|
||||
# move this node to the top of the parent tree (typically a scene's root node) so that it is
|
||||
# called before all other _physics_processes
|
||||
parent.move_child(self, 0)
|
||||
|
||||
if smooth_parent:
|
||||
process_priority = parent.process_priority - 1
|
||||
|
||||
# update the relevant nodes once per _physics_process
|
||||
_physics_process_nodes = _get_physics_process_nodes(parent, !smooth_parent)
|
||||
|
||||
# clean up _properties
|
||||
for key in _properties.keys():
|
||||
if !_physics_process_nodes.has(key):
|
||||
_properties.erase(key)
|
||||
|
||||
for node in _physics_process_nodes:
|
||||
if !_properties.has(node):
|
||||
# called on the first frame after a node was added to _properties
|
||||
_properties[node] = {}
|
||||
|
||||
# clean up _properties when a node exited the tree
|
||||
node.tree_exited.connect(func (): _properties.erase(node))
|
||||
|
||||
for property in properties:
|
||||
if ! property in node: continue
|
||||
|
||||
if !_properties[node].has(property):
|
||||
# called on the first frame after a node was added to _properties
|
||||
_properties[node][property] = [node[property]]
|
||||
elif _properties[node][property].size() < 2:
|
||||
# called on the second frame after a node was added to _properties
|
||||
_properties[node][property].push_front(_properties[node][property][0])
|
||||
_properties[node][property][1] = node[property]
|
||||
else:
|
||||
_properties[node][property][0] = _properties[node][property][1]
|
||||
node[property] = _properties[node][property][0]
|
||||
|
||||
_physics_process_just_updated = true
|
||||
|
||||
|
||||
## Get the relevant nodes to be smoothed based on this node's tree position and properties.
|
||||
func _get_physics_process_nodes(node: Node, ignore_node: = false, with_includes: = true) -> Array[Node]:
|
||||
var nodes:Array[Node] = []
|
||||
|
||||
nodes.assign(includes.map(
|
||||
get_node_or_null
|
||||
).filter(
|
||||
func (_node:Node) -> bool: return _node != null && !excludes.has(get_path_to(_node))
|
||||
) if with_includes else [])
|
||||
|
||||
if (
|
||||
!ignore_node
|
||||
&& node != self
|
||||
&& !node is RigidBody2D
|
||||
&& !node is RigidBody3D
|
||||
&& !nodes.has(node)
|
||||
&& !excludes.has(get_path_to(node))
|
||||
&& node.has_method("_physics_process")
|
||||
):
|
||||
nodes.push_back(node)
|
||||
|
||||
if recursive:
|
||||
for child in node.get_children():
|
||||
for nested_node in _get_physics_process_nodes(child, false, false):
|
||||
_add_unique_to_array(nodes, nested_node)
|
||||
|
||||
return nodes
|
|
@ -0,0 +1 @@
|
|||
https://www.hippopng.com/png-btykal/
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Common functions useful for exporting blender scenes to GLTF with bpy
|
||||
"""
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
from sys import stderr
|
||||
import bpy
|
||||
from bpy.types import Object, Armature, Modifier, Mesh, Context, Collection
|
||||
from functools import reduce
|
||||
from operator import attrgetter, itemgetter, or_
|
||||
|
||||
|
||||
|
||||
def clear_selection(ctx: Context):
|
||||
for obj in ctx.selected_objects:
|
||||
obj.select_set(False)
|
||||
|
||||
|
||||
def select_collection(ctx: Context, c: Collection):
|
||||
for obj in c.objects:
|
||||
obj.select_set(True)
|
||||
|
||||
# https://blenderartists.org/t/how-to-apply-all-the-modifiers-with-python/1314483/2
|
||||
|
||||
|
||||
def apply_modifier(obj: Object, modifier: Modifier):
|
||||
with bpy.context.temp_override(object=obj, modifier=modifier):
|
||||
bpy.context.view_layer.update()
|
||||
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
|
||||
def sort_uv_layers(mesh: Mesh):
|
||||
# UV layers use non-pythonic reference semantics, safest to always reference by their indices
|
||||
assert len(
|
||||
mesh.uv_layers) <= 7, "There must be fewer than 8 UV maps to sort (one less than the Blender maximum of 8)"
|
||||
initial_layers = [[i, layer.name]
|
||||
for i, layer in enumerate(mesh.uv_layers)]
|
||||
initial_layers.sort(key=itemgetter(1))
|
||||
for i, layer_name in tuple(initial_layers):
|
||||
assert mesh.uv_layers[i].name == layer_name, mesh.uv_layers[i].name + \
|
||||
" " + layer_name
|
||||
mesh.uv_layers.active = mesh.uv_layers[i]
|
||||
new_layer = mesh.uv_layers.new(name=f"new_{mesh.uv_layers[i].name}")
|
||||
assert new_layer == mesh.uv_layers[-1]
|
||||
mesh.uv_layers.active = mesh.uv_layers[0]
|
||||
mesh.uv_layers.remove(mesh.uv_layers[i])
|
||||
for pair in initial_layers:
|
||||
if pair[0] > i:
|
||||
pair[0] -= 1
|
||||
for layer in mesh.uv_layers:
|
||||
layer.name = layer.name.replace("new_", "")
|
||||
mesh.uv_layers.active = mesh.uv_layers[0]
|
||||
|
||||
def delete_uv_layer(mesh: Mesh, name: str):
|
||||
for layer in mesh.uv_layers:
|
||||
if layer.name == name:
|
||||
mesh.uv_layers.remove(layer)
|
||||
return
|
||||
raise Exception(f"failed to remove UV layer with name {name}")
|
||||
|
||||
def convert_attribute(obj, attribute_name: str):
|
||||
"Don't convert this into a function that does multiple at once, indices are too fucky for that"
|
||||
for i, attr in enumerate(obj.data.attributes):
|
||||
if attr.name == attribute_name:
|
||||
obj.data.attributes.active_index = i
|
||||
bpy.context.view_layer.update()
|
||||
bpy.ops.geometry.attribute_convert(
|
||||
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
|
||||
bpy.context.view_layer.update()
|
||||
return
|
||||
raise Exception("attribute not found", attribute_name)
|
||||
|
||||
|
||||
def apply_bone_groups(rig: Armature, skinned: Object):
|
||||
"""May not be needed in future versions of Blender
|
||||
Converts geometry nodes attributes for bone groups back to vertex groups
|
||||
Something fishy with indices and attributes, so algorithm is not efficient just to be safe
|
||||
"""
|
||||
with bpy.context.temp_override(object=skinned):
|
||||
for bone_name in map(attrgetter("name"), rig.bones):
|
||||
for i, attribute in enumerate(skinned.data.attributes):
|
||||
if attribute.name == bone_name:
|
||||
skinned.data.attributes.active_index = i
|
||||
bpy.context.view_layer.update()
|
||||
bpy.ops.geometry.attribute_convert(
|
||||
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
|
||||
bpy.context.view_layer.update()
|
||||
break
|
||||
else:
|
||||
print("Failed to find attribute for", bone_name, file=stderr)
|
|
@ -0,0 +1,29 @@
|
|||
extends Node3D
|
||||
|
||||
const MOUSE_SENSITIVITY := .001
|
||||
|
||||
func get_input_direction() -> Vector2:
|
||||
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
if input == Vector2.ZERO:
|
||||
return Vector2.ZERO
|
||||
var dir := input.normalized()
|
||||
|
||||
input = input * dir.abs()
|
||||
return -1.0 * dir.rotated(-rotation.y + PI)
|
||||
|
||||
func _process(_delta):
|
||||
get_parent().movement_dir = get_input_direction()
|
||||
%SpringArm3D.look_at(global_position)
|
||||
|
||||
func _ready():
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
func handle_camera_movement(move: Vector2):
|
||||
if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
move *= MOUSE_SENSITIVITY
|
||||
rotate_y(-move.x)
|
||||
|
||||
func _input(event):
|
||||
if event is InputEventMouseMotion:
|
||||
handle_camera_movement(event.relative)
|
|
@ -0,0 +1,15 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://brgqf2ebuyhuy"]
|
||||
|
||||
[ext_resource type="Script" path="res://camera/camera.gd" id="1_veqr4"]
|
||||
|
||||
[node name="Camera" type="Node3D"]
|
||||
script = ExtResource("1_veqr4")
|
||||
|
||||
[node name="SpringArm3D" type="SpringArm3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 2)
|
||||
spring_length = 2.0
|
||||
margin = 0.5
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="SpringArm3D"]
|
||||
unique_name_in_owner = true
|
|
@ -0,0 +1 @@
|
|||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" fill="#478cbf"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>
|
After Width: | Height: | Size: 949 B |
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cddalye5wfvl6"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
Binary file not shown.
|
@ -0,0 +1,47 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bq654gwim6col"
|
||||
path="res://.godot/imported/level.glb-f09dab4240afab499892fe27d8f400a9.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://level/level.glb"
|
||||
dest_files=["res://.godot/imported/level.glb-f09dab4240afab499892fe27d8f400a9.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
import_script/path=""
|
||||
_subresources={
|
||||
"materials": {
|
||||
"Sand": {
|
||||
"use_external/enabled": true,
|
||||
"use_external/path": "res://level/materials/Sand.tres"
|
||||
}
|
||||
},
|
||||
"nodes": {
|
||||
"PATH:Cube": {
|
||||
"generate/physics": true,
|
||||
"physics/shape_type": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
|
@ -0,0 +1,46 @@
|
|||
[gd_scene load_steps=8 format=3 uid="uid://b00brfkibo5cj"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bq654gwim6col" path="res://level/level.glb" id="1_s37in"]
|
||||
[ext_resource type="PackedScene" uid="uid://do25xvpy80iio" path="res://player/player.tscn" id="2_7ct70"]
|
||||
[ext_resource type="Environment" uid="uid://covjrwmk4rplw" path="res://level/world_environment.tres" id="2_ptkl6"]
|
||||
[ext_resource type="PackedScene" uid="uid://brgqf2ebuyhuy" path="res://camera/camera.tscn" id="3_yev7j"]
|
||||
[ext_resource type="Script" path="res://addons/smoother/smoother.gd" id="5_2tyle"]
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_ujmev"]
|
||||
margin = 2.067
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_qp06x"]
|
||||
size = Vector3(2, 0.1, 2)
|
||||
|
||||
[node name="level" instance=ExtResource("1_s37in")]
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." index="0"]
|
||||
environment = ExtResource("2_ptkl6")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.566018, 0.824393, 0, -0.824393, 0.566018, 0, 13.4573, 0)
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="Plane" index="0"]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Plane/StaticBody3D" index="0"]
|
||||
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
|
||||
shape = SubResource("WorldBoundaryShape3D_ujmev")
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="Plane/StaticBody3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||
shape = SubResource("BoxShape3D_qp06x")
|
||||
|
||||
[node name="Player" parent="." index="3" instance=ExtResource("2_7ct70")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.36667, 2.66437, 0)
|
||||
floor_max_angle = 1.309
|
||||
JUMP_VELOCITY = 5.0
|
||||
|
||||
[node name="Camera" parent="Player" index="2" instance=ExtResource("3_yev7j")]
|
||||
|
||||
[node name="Players" type="Node3D" parent="." index="4"]
|
||||
|
||||
[node name="Smoother" type="Node" parent="." index="5"]
|
||||
script = ExtResource("5_2tyle")
|
||||
|
||||
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." index="6"]
|
||||
spawn_path = NodePath("../Players")
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://babdfqbjlgnn"
|
||||
path.s3tc="res://.godot/imported/level_sand.png-c6e8f007b14b98f18a4346ab72e8719d.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "dd9f28b10e384aa5f76555a302d90a36"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://level/level_sand.png"
|
||||
dest_files=["res://.godot/imported/level_sand.png-c6e8f007b14b98f18a4346ab72e8719d.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
|
@ -0,0 +1,10 @@
|
|||
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://b3nngj6l17khj"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://babdfqbjlgnn" path="res://level/level_sand.png" id="1_p08dh"]
|
||||
|
||||
[resource]
|
||||
resource_name = "Sand"
|
||||
cull_mode = 2
|
||||
albedo_texture = ExtResource("1_p08dh")
|
||||
roughness = 0.5
|
||||
uv1_scale = Vector3(3.67, 3.67, 3.67)
|
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://445svcjk41fn"
|
||||
path="res://.godot/imported/sand.png-20c37fc6a1ebebf936f842368a0c94a6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://level/materials/sand.png"
|
||||
dest_files=["res://.godot/imported/sand.png-20c37fc6a1ebebf936f842368a0c94a6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
|
@ -0,0 +1,11 @@
|
|||
[gd_resource type="Environment" load_steps=3 format=3 uid="uid://covjrwmk4rplw"]
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_5o4sp"]
|
||||
|
||||
[sub_resource type="Sky" id="Sky_nb6lr"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_5o4sp")
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_nb6lr")
|
||||
ambient_light_source = 3
|
|
@ -0,0 +1,27 @@
|
|||
# Automatically Generated From Builtin CharacterBody Template
|
||||
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var SPEED = 5.0
|
||||
@export var JUMP_VELOCITY = 4.5
|
||||
|
||||
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
# Set by camera child, switch to signals if becomes too spaghetti
|
||||
var movement_dir: Vector2 = Vector2.ZERO
|
||||
|
||||
func _physics_process(delta):
|
||||
if not is_on_floor():
|
||||
velocity.y -= gravity * delta
|
||||
|
||||
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
||||
velocity.y = JUMP_VELOCITY
|
||||
|
||||
if movement_dir:
|
||||
velocity.x = movement_dir.x * SPEED
|
||||
velocity.z = movement_dir.y * SPEED
|
||||
else:
|
||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
||||
velocity.z = move_toward(velocity.z, 0, SPEED)
|
||||
|
||||
move_and_slide()
|
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bwg2jkbq7gada"
|
||||
path="res://.godot/imported/player.glb-d5e59c3624fa2635da7ea043f9526ccc.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://player/player.glb"
|
||||
dest_files=["res://.godot/imported/player.glb-d5e59c3624fa2635da7ea043f9526ccc.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type="CharacterBody3D"
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
|
@ -0,0 +1,24 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://do25xvpy80iio"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bwg2jkbq7gada" path="res://player/player.glb" id="1_0u2un"]
|
||||
[ext_resource type="Script" path="res://player/player.gd" id="1_gh340"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_u4bmc"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath("player:velocity")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_t1htn"]
|
||||
radius = 0.986757
|
||||
|
||||
[node name="player" instance=ExtResource("1_0u2un")]
|
||||
script = ExtResource("1_gh340")
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="0"]
|
||||
replication_config = SubResource("SceneReplicationConfig_u4bmc")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="2"]
|
||||
shape = SubResource("SphereShape3D_t1htn")
|
|
@ -0,0 +1,38 @@
|
|||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Grounders"
|
||||
config/features=PackedStringArray("4.2", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[input]
|
||||
|
||||
move_left={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue