├── .gitignore
├── Input buffer
├── Input buffer.csproj
├── Input buffer.sln
├── InputBuffer.cs
├── demo
│ ├── BufferControl.cs
│ ├── Cloud.cs
│ ├── Dino.cs
│ ├── DinoGameAnimator.cs
│ ├── Ground.cs
│ ├── PressStart2P-Regular.ttf
│ ├── Treadmill.cs
│ ├── cloud.tscn
│ ├── cooler_icon.png
│ ├── cooler_icon.png.import
│ ├── default_env.tres
│ ├── icon.png
│ ├── icon.png.import
│ ├── main.tscn
│ ├── obstacles
│ │ ├── Cactus.cs
│ │ ├── Pterodactyl.cs
│ │ ├── cactus.tscn
│ │ ├── cactus_baby.tscn
│ │ ├── cactus_clump.tscn
│ │ ├── pterodactyl.tscn
│ │ └── test_obstacle.tscn
│ ├── offline-sprite-2x.png
│ ├── offline-sprite-2x.png.import
│ ├── sfx
│ │ ├── die.wav
│ │ ├── die.wav.import
│ │ ├── jump.wav
│ │ ├── jump.wav.import
│ │ ├── point.wav
│ │ └── point.wav.import
│ ├── systems
│ │ ├── NodeExtensions.cs
│ │ ├── PauseControl.cs
│ │ ├── RandomNumberGeneratorExtensions.cs
│ │ └── StateMachine.cs
│ └── ui
│ │ ├── GameOverMessage.cs
│ │ ├── HighScore.cs
│ │ └── Score.cs
├── input_buffer.gd
└── project.godot
├── LICENSE
├── README.md
└── demo_screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Godot-specific ignores
3 | .import/
4 | export.cfg
5 | export_presets.cfg
6 |
7 | # Mono-specific ignores
8 | .mono/
9 | data_*/
10 |
--------------------------------------------------------------------------------
/Input buffer/Input buffer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net472
4 | Inputbuffer
5 |
6 |
--------------------------------------------------------------------------------
/Input buffer/Input buffer.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 2012
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Input buffer", "Input buffer.csproj", "{F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}"
4 | EndProject
5 | Global
6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
7 | Debug|Any CPU = Debug|Any CPU
8 | ExportDebug|Any CPU = ExportDebug|Any CPU
9 | ExportRelease|Any CPU = ExportRelease|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
15 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
16 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
17 | {F3C09B12-0FCA-41FE-AC2F-D134AE2D15E0}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
18 | EndGlobalSection
19 | EndGlobal
20 |
--------------------------------------------------------------------------------
/Input buffer/InputBuffer.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | ///
6 | /// Keeps track of recent inputs in order to make timing windows more flexible.
7 | /// Intended use: Add this file to your project as an AutoLoad script and have other objects call the class' static
8 | /// methods.
9 | /// (more on AutoLoad: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html)
10 | ///
11 | public class InputBuffer : Node
12 | {
13 | ///
14 | /// How many milliseconds ahead of time the player can make an input and have it still be recognized.
15 | /// I chose the value 150 because it imitates the 9-frame buffer window in the Super Smash Bros. Ultimate game.
16 | ///
17 | private static readonly ulong BUFFER_WINDOW = 150;
18 |
19 | /// Tells when each keyboard key was last pressed.
20 | private static Dictionary _keyboardTimestamps;
21 | /// Tells when each joypad (controller) button was last pressed.
22 | private static Dictionary _joypadTimestamps;
23 |
24 | ///
25 | /// Called when the node enters the scene tree for the first time.
26 | ///
27 | public override void _Ready()
28 | {
29 | PauseMode = PauseModeEnum.Process;
30 |
31 | // Initialize all dictionary entries.
32 | _keyboardTimestamps = new Dictionary();
33 | _joypadTimestamps = new Dictionary();
34 | }
35 |
36 | ///
37 | /// Called whenever the player makes an input.
38 | ///
39 | /// Object containing information about the input.
40 | public override void _Input(InputEvent @event)
41 | {
42 | // Get a numerical representation of the input and store it in the timestamp dictionary.
43 | if (@event is InputEventKey)
44 | {
45 | InputEventKey eventKey = @event as InputEventKey;
46 | if (!eventKey.Pressed || eventKey.IsEcho()) return;
47 |
48 | uint scancode = eventKey.Scancode;
49 |
50 | if (_keyboardTimestamps.ContainsKey(scancode))
51 | {
52 | // The Time.GetTicksMsec() value will wrap after roughly 500 million years. TODO: Implement a very slow
53 | // memory leak so the game crashes before that happens.
54 | _keyboardTimestamps[scancode] = Time.GetTicksMsec();
55 | }
56 | else
57 | {
58 | _keyboardTimestamps.Add(scancode, Time.GetTicksMsec());
59 | }
60 | }
61 | else if (@event is InputEventJoypadButton)
62 | {
63 | InputEventJoypadButton eventJoypadButton = @event as InputEventJoypadButton;
64 | if (!eventJoypadButton.Pressed || eventJoypadButton.IsEcho()) return;
65 |
66 | int buttonIndex = eventJoypadButton.ButtonIndex;
67 |
68 | if (_joypadTimestamps.ContainsKey(buttonIndex))
69 | {
70 | _joypadTimestamps[buttonIndex] = Time.GetTicksMsec();
71 | }
72 | else
73 | {
74 | _joypadTimestamps.Add(buttonIndex, Time.GetTicksMsec());
75 | }
76 | }
77 | }
78 |
79 | ///
80 | /// Returns whether any of the keyboard keys or joypad buttons in the given action were pressed within the buffer
81 | /// window.
82 | ///
83 | /// The action to check for in the buffer.
84 | ///
85 | /// True if any of the action's associated keys/buttons were pressed within the buffer window, false otherwise.
86 | ///
87 | public static bool IsActionPressBuffered(string action)
88 | {
89 | /*
90 | Get the inputs associated with the action. If any one of them was pressed in the last BUFFER_WINDOW
91 | milliseconds, the action is buffered.
92 | */
93 | foreach (InputEvent @event in InputMap.GetActionList(action))
94 | {
95 | if (@event is InputEventKey)
96 | {
97 | InputEventKey eventKey = @event as InputEventKey;
98 | uint scancode = eventKey.Scancode;
99 | if (_keyboardTimestamps.ContainsKey(scancode))
100 | {
101 | if (Time.GetTicksMsec() - _keyboardTimestamps[scancode] <= BUFFER_WINDOW)
102 | {
103 | // Prevent this method from returning true repeatedly and registering duplicate actions.
104 | InvalidateAction(action);
105 |
106 | return true;
107 | }
108 | }
109 | }
110 | else if (@event is InputEventJoypadButton)
111 | {
112 | InputEventJoypadButton eventJoypadButton = @event as InputEventJoypadButton;
113 | int buttonIndex = eventJoypadButton.ButtonIndex;
114 | if (_joypadTimestamps.ContainsKey(buttonIndex))
115 | {
116 | if (Time.GetTicksMsec() - _joypadTimestamps[buttonIndex] <= BUFFER_WINDOW)
117 | {
118 | InvalidateAction(action);
119 | return true;
120 | }
121 | }
122 | }
123 | }
124 | /*
125 | If there's ever a third type of buffer-able action (mouse clicks maybe?), it'd probably be worth it to
126 | generalize the repetitive keyboard/joypad code into something that works for any input method. Until then, by
127 | the YAGNI principle, the repetitive stuff stays >:)
128 | */
129 |
130 | return false;
131 | }
132 |
133 | ///
134 | /// Records unreasonable timestamps for all the inputs in an action. Called when IsActionPressBuffered returns true,
135 | /// as otherwise it would continue returning true every frame for the rest of the buffer window.
136 | ///
137 | /// The action whose input to invalidate.
138 | public static void InvalidateAction(string action)
139 | {
140 | foreach (InputEvent @event in InputMap.GetActionList(action))
141 | {
142 | if (@event is InputEventKey)
143 | {
144 | InputEventKey eventKey = @event as InputEventKey;
145 | uint scancode = eventKey.Scancode;
146 | if (_keyboardTimestamps.ContainsKey(scancode))
147 | {
148 | _keyboardTimestamps[scancode] = 0;
149 | }
150 | }
151 | else if (@event is InputEventJoypadButton)
152 | {
153 | InputEventJoypadButton eventJoypadButton = @event as InputEventJoypadButton;
154 | int buttonIndex = eventJoypadButton.ButtonIndex;
155 | if (_joypadTimestamps.ContainsKey(buttonIndex))
156 | {
157 | _joypadTimestamps[buttonIndex] = 0;
158 | }
159 | }
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/Input buffer/demo/BufferControl.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// Toggles the input buffer on and off.
6 | ///
7 | public class BufferControl : Label
8 | {
9 | ///
10 | /// Signal emitted when the buffer is toggled.
11 | ///
12 | /// True when the buffer is turned on, false when it's turned off.
13 | [Signal] private delegate void BufferToggled(bool on);
14 |
15 | private bool _bufferOn = true;
16 | private AnimationPlayer _animator; [Export] private NodePath _animatorPath;
17 |
18 | ///
19 | /// Called when the node enters the scene tree for the first time.
20 | ///
21 | public override void _Ready()
22 | {
23 | Modulate = new Color("00ffffff");
24 | _animator = GetNode(_animatorPath);
25 | }
26 |
27 | ///
28 | /// Called every frame.
29 | ///
30 | /// The elapsed time since the previous frame.
31 | public override void _Process(float delta)
32 | {
33 | if (Input.IsActionJustPressed("ui_cancel"))
34 | {
35 | _bufferOn = !_bufferOn;
36 |
37 | if (_bufferOn)
38 | {
39 | Text = "Input buffer ON";
40 | }
41 | else
42 | {
43 | Text = "Input buffer OFF";
44 | }
45 | if (_animator.IsPlaying())
46 | {
47 | _animator.Seek(0);
48 | }
49 | else
50 | {
51 | _animator.Play("Reveal");
52 | }
53 |
54 | EmitSignal(nameof(BufferToggled), new object[] { _bufferOn });
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Input buffer/demo/Cloud.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// A cloud that lazily moves across the background
6 | ///
7 | public class Cloud : Node2D
8 | {
9 | /// Just how lazily the cloud moves across the background
10 | [Export] private float _speed = 50;
11 | /// When respawning, the cloud can go between 0 and this many units above the respawn point.
12 | [Export] private float _maxYOffset = 150;
13 | /// Where to teleport to after leaving the screen
14 | private Position2D _respawnPoint; [Export] private NodePath _respawnPointPath = null;
15 | private bool _moving = false;
16 | private RandomNumberGenerator _rng;
17 |
18 | ///
19 | /// Called when the node enters the scene tree for the first time.
20 | ///
21 | public override void _Ready()
22 | {
23 | _respawnPoint = GetNode(_respawnPointPath);
24 | _rng = new RandomNumberGenerator();
25 | _rng.Randomize();
26 | }
27 |
28 | ///
29 | /// Called every frame.
30 | ///
31 | /// The elapsed time since the previous frame.
32 | public override void _Process(float delta)
33 | {
34 | if (_moving)
35 | {
36 | Position += Vector2.Left * _speed * delta;
37 | }
38 | }
39 |
40 | ///
41 | /// One of the methods of all time.
42 | ///
43 | public void Start() => _moving = true;
44 | ///
45 | /// :)
46 | ///
47 | public void _on_Dino_GotHit() => _moving = false;
48 |
49 | ///
50 | /// Teleports the cloud to the right edge of the screen when it exits the screen.
51 | ///
52 | /// The area that represents the edge of the screen.
53 | /// Keep in mind that the colliders at the edge of the screen don't have classes, so this class uses collision
54 | /// layers to make sure that it only wraps around when it exits the screen.
55 | public void _on_Area2D_area_exited(Area2D area)
56 | {
57 | Position = _respawnPoint.Position;
58 | Position += Vector2.Down * _rng.RandiRange(0, (int)_maxYOffset);
59 | }
60 | }
--------------------------------------------------------------------------------
/Input buffer/demo/Dino.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | ///
6 | /// The game's brave protagonist.
7 | ///
8 | public class Dino : KinematicBody2D
9 | {
10 | ///
11 | /// After doing the jump to begin the game, we emit this signal so other nodes in the scene can finish the intro
12 | /// animation.
13 | ///
14 | [Signal] private delegate void IntroJumpFinished();
15 | /// Emitted after hitting an obstacle
16 | [Signal] private delegate void GotHit();
17 |
18 | private enum DinoState
19 | {
20 | Idle,
21 | IntroAnimation,
22 | Grounded,
23 | Jumping,
24 | Ducking,
25 | Dead
26 | }
27 |
28 | private static readonly string JUMP_ACTION = "ui_accept";
29 | private static readonly string DUCK_ACTION = "ui_down";
30 | private static readonly string RUN_ANIMATION = "Run";
31 |
32 | private StateMachine _stateMachine;
33 | private AnimationPlayer _animator; [Export] private NodePath _animation_player_path = null;
34 | private CollisionShape2D _regularHitbox; [Export] private NodePath _regularHitboxPath = null;
35 | private CollisionShape2D _duckingHitbox; [Export] private NodePath _duckingHitboxPath = null;
36 | private AudioStreamPlayer _audio; [Export] private NodePath _audioPath = null;
37 | private Vector2 _velocity;
38 | private float _gravity;
39 | private bool _useBuffer = true;
40 | private float _initialY;
41 |
42 | /// How many pixels per second squared the dino accelerates towards the ground at normally.
43 | [Export] private float _regularGravity = 2400f;
44 | ///
45 | /// How many pixels per second squared the dino accelerates towards the ground at if the player releases the jump
46 | /// button while rising.
47 | ///
48 | [Export] private float _shortHopGravity = 4800f;
49 | ///
50 | /// How many pixels per second squared the dino accelerates towards the ground at if the player presses and holds
51 | /// the down button.
52 | ///
53 | [Export] private float _fastFallGravity = 9600f;
54 | ///
55 | /// Pixels per second downward the dino moves the moment it jumps.
56 | /// Recall that the coordinate system has the positive y axis point down, so this should be negative.
57 | ///
58 | [Export] private float _initialJumpSpeed = 800f;
59 |
60 | ///
61 | /// Called when the node enters the scene tree for the first time.
62 | ///
63 | public override void _Ready()
64 | {
65 | _animator = GetNode(_animation_player_path);
66 | _regularHitbox = GetNode(_regularHitboxPath);
67 | _duckingHitbox = GetNode(_duckingHitboxPath);
68 | _audio = GetNode(_audioPath);
69 | _initialY = Position.y;
70 |
71 | _stateMachine = new StateMachine(
72 | new Dictionary
73 | {
74 | { DinoState.Idle, new StateSpec(enter: IdleEnter, update: IdlePhysicsProcess) },
75 | { DinoState.IntroAnimation, new StateSpec(
76 | enter: IntroAnimationEnter, update: IntroAnimationPhysicsProcess, exit: IntroAnimationExit) },
77 | { DinoState.Grounded, new StateSpec(enter: GroundedEnter, update: GroundedPhysicsProcess)},
78 | { DinoState.Jumping, new StateSpec(enter: JumpingEnter, update: JumpingPhysicsProcess)},
79 | { DinoState.Ducking, new StateSpec(
80 | enter: DuckingEnter, update: DuckingPhysicsProcess, exit: DuckingExit) },
81 | { DinoState.Dead, new StateSpec(enter: Die)}
82 | },
83 | DinoState.Idle
84 | );
85 |
86 | _animator.Play("Idle + Jump");
87 | }
88 |
89 | ///
90 | /// Called during the physics processing step of the main loop.
91 | ///
92 | /// The elapsed time since the previous physics step.
93 | public override void _PhysicsProcess(float delta)
94 | {
95 | base._PhysicsProcess(delta);
96 |
97 | _stateMachine.Update(delta);
98 | }
99 |
100 | // Signal callbacks.
101 | private void _on_Regular_hitbox_area_entered(Area2D area) => OnObstacleHit();
102 | private void _on_Ducking_hitbox_area_entered(Area2D area) => OnObstacleHit();
103 | private void _on_Buffer_control_BufferToggled(bool on) => _useBuffer = on;
104 | private void _on_Retry_button_pressed()
105 | {
106 | Position = new Vector2(Position.x, _initialY);
107 | InputBuffer.InvalidateAction(JUMP_ACTION);
108 | _stateMachine.TransitionTo(DinoState.Grounded);
109 | }
110 |
111 | ///
112 | /// Called when colliding with an obstacle.
113 | ///
114 | /// The obstacle's collider.
115 | private void OnObstacleHit()
116 | {
117 | EmitSignal(nameof(GotHit), new object[0]);
118 | _stateMachine.TransitionTo(DinoState.Dead);
119 | }
120 |
121 | // Idle state callbacks.
122 | private void IdleEnter()
123 | {
124 | _animator.Play("Idle + Jump");
125 | }
126 | private void IdlePhysicsProcess(float delta)
127 | {
128 | bool jumping;
129 | if (_useBuffer)
130 | {
131 | jumping = InputBuffer.IsActionPressBuffered(JUMP_ACTION);
132 | }
133 | else
134 | {
135 | jumping = Input.IsActionJustPressed(JUMP_ACTION);
136 | }
137 |
138 | if (jumping)
139 | {
140 | _stateMachine.TransitionTo(DinoState.IntroAnimation);
141 | }
142 | }
143 |
144 | // Intro animation state callbacks.
145 | private void IntroAnimationEnter()
146 | {
147 | _velocity = _initialJumpSpeed * Vector2.Up;
148 | _gravity = _regularGravity;
149 | _audio.Play();
150 | }
151 | private void IntroAnimationPhysicsProcess(float delta)
152 | {
153 | // Move and detect collision with the ground.
154 | _velocity += _gravity * delta * Vector2.Down;
155 | MoveAndSlide(_velocity, Vector2.Up);
156 | if (IsOnFloor())
157 | {
158 | _stateMachine.TransitionTo(DinoState.Grounded);
159 | }
160 | }
161 | private void IntroAnimationExit()
162 | {
163 | /*
164 | Move the dino forward a bit as part of the intro animation. It's defined here instead of in the capital-A
165 | Animation in order to only set the position's x component and make sure the dino is still able to jump.
166 | */
167 | SceneTreeTween introAnimationDrift = CreateTween();
168 | introAnimationDrift.TweenProperty(this, "position:x", 154f, 0.7f);
169 | introAnimationDrift.Play();
170 |
171 | EmitSignal(nameof(IntroJumpFinished), new object[0]);
172 | }
173 |
174 | // Grounded state callbacks.
175 | private void GroundedEnter()
176 | {
177 | _animator.Play(RUN_ANIMATION);
178 | }
179 | private void GroundedPhysicsProcess(float delta)
180 | {
181 | bool jumping;
182 | if (_useBuffer)
183 | {
184 | jumping = InputBuffer.IsActionPressBuffered(JUMP_ACTION);
185 | }
186 | else
187 | {
188 | jumping = Input.IsActionJustPressed(JUMP_ACTION);
189 | }
190 |
191 | if (jumping)
192 | {
193 | _stateMachine.TransitionTo(DinoState.Jumping);
194 | }
195 | else if (Input.IsActionPressed(DUCK_ACTION))
196 | {
197 | _stateMachine.TransitionTo(DinoState.Ducking);
198 | }
199 | }
200 |
201 | // Jumping state callbacks.
202 | private void JumpingEnter()
203 | {
204 | _velocity = _initialJumpSpeed * Vector2.Up;
205 | _gravity = _regularGravity;
206 | _animator.Play("Idle + Jump");
207 | _audio.Play();
208 | }
209 | private void JumpingPhysicsProcess(float delta)
210 | {
211 | // Increase gravity if the player releases the jump button while rising.
212 | if (Input.IsActionJustReleased(JUMP_ACTION) && _velocity.Dot(Vector2.Up) > 0)
213 | {
214 | _gravity = _shortHopGravity;
215 | }
216 |
217 | // Reset the gravity once the dino begins falling after a short hop.
218 | if (_velocity.Dot(Vector2.Up) < 0)
219 | {
220 | _gravity = _regularGravity;
221 | }
222 |
223 | /*
224 | Fast fall by pressing the down button.
225 | With this implementation, the player can cancel a fast fall by releasing the button, but hey, that's how the
226 | original worked ¯\_(ツ)_/¯
227 | */
228 | if (Input.IsActionPressed("ui_down"))
229 | {
230 | _gravity = _fastFallGravity;
231 | }
232 |
233 | // Move and detect collision with the ground.
234 | _velocity += _gravity * delta * Vector2.Down;
235 | MoveAndSlide(_velocity, Vector2.Up);
236 | if (IsOnFloor())
237 | {
238 | _stateMachine.TransitionTo(DinoState.Grounded);
239 | }
240 | }
241 |
242 | // Ducking state callbacks.
243 | private void DuckingEnter()
244 | {
245 | _animator.Play("Ducking");
246 | _regularHitbox.SetDeferred("disabled", true);
247 | _duckingHitbox.SetDeferred("disabled", false);
248 | }
249 | private void DuckingPhysicsProcess(float delta)
250 | {
251 | if (!Input.IsActionPressed(DUCK_ACTION))
252 | {
253 | _stateMachine.TransitionTo(DinoState.Grounded);
254 | }
255 | }
256 | private void DuckingExit()
257 | {
258 | _regularHitbox.SetDeferred("disabled", false);
259 | _duckingHitbox.SetDeferred("disabled", true);
260 | }
261 |
262 | // Dead state callback :(
263 | private void Die()
264 | {
265 | _animator.Play("Die");
266 | _animator.Advance(0);
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/Input buffer/demo/DinoGameAnimator.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// An experiment in giving a scene a 'global' animation player that can manipulate any node in the scene.
6 | /// Will this reduce coupling (by replacing dependencies in code with AnimationPlayer tracks)? Or will it turn the scene
7 | /// into an inscrutable mess (by nature of a god object that has access to everything)? Time to find out!
8 | ///
9 | /// A bit of elaboration: I think this is a good idea because the alternative is to skip this middle layer and make
10 | /// objects communicate with each other using their signals directly. So, there's gonna be coupling in the editor either
11 | /// way, and I'd rather put it all in an AnimationPlayer than spread it across the scene. Plus, putting it in an
12 | /// animation makes it waaaay easier to manage timing.
13 | public class DinoGameAnimator : AnimationPlayer
14 | {
15 | private static readonly string INTRO_ANIMATION = "Intro animation";
16 |
17 | /// Gets the game ready to play.
18 | private void _on_Dino_IntroJumpFinished()
19 | {
20 | Play(INTRO_ANIMATION);
21 | }
22 |
23 | /// Gets the game ready to play again.
24 | private void _on_Retry_button_pressed()
25 | {
26 | /*
27 | Playing the last split second of the animation allows us to essentially apply the animation's effects instantly.
28 | */
29 | CurrentAnimation = INTRO_ANIMATION;
30 | Advance(CurrentAnimationLength - 0.1f);
31 | Play();
32 | }
33 | }
--------------------------------------------------------------------------------
/Input buffer/demo/Ground.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 | using ExtensionMethods;
5 |
6 | ///
7 | /// A sprite representing the ground the dino runs across.
8 | ///
9 | public class Ground : Sprite
10 | {
11 | /// The obstacles that can appear on the ground
12 | [Export] private PackedScene _cactus = null, _cactusClump = null, _cactusBaby = null, _pterodactyl = null;
13 | ///
14 | /// Where to spawn the first obstacle when the ground is initialized.
15 | /// Useful to give the player a bit of space at the very start of the game.
16 | ///
17 | [Export] private int _initialObstaclePos = 2000;
18 | ///
19 | /// Reference to the other ground sprite. When one sprite goes offscreen, it teleports behind the other one to give
20 | /// the appearance of a single endless ground sprite.
21 | ///
22 | private Ground _otherGround; [Export] private NodePath _otherGroundPath = null;
23 | /// The obstacles on this segment of the ground.
24 | private List _obstacles = new List();
25 | /// Whether it's legal to spawn pterodactyls.
26 | private bool _canSpawnPterodactyls = false;
27 |
28 | // Called when the node enters the scene tree for the first time.
29 | public override void _Ready()
30 | {
31 | _otherGround = GetNode(_otherGroundPath);
32 | SpawnObstacles(_initialObstaclePos);
33 | }
34 |
35 | ///
36 | /// Removes all obstacles that belong to this slice of the ground.
37 | ///
38 | public void DespawnObstacles()
39 | {
40 | foreach (Node obstacle in _obstacles)
41 | {
42 | obstacle.QueueFree();
43 | }
44 | _obstacles.Clear();
45 | }
46 |
47 | ///
48 | /// Spawns obstacles along this slice of the ground. Note that this doesn't clear out existing obstacles.
49 | ///
50 | /// Offset from the left edge of the ground at which to start placing obstacles.
51 | private void SpawnObstacles(int startPos = 400)
52 | {
53 | // Min/max pixels between obstacles
54 | const int MIN_OFFSET = 450, MAX_OFFSET = 800;
55 | // The current position to spawn an obstacle at
56 | int currentPos = startPos;
57 | // Used to randomly spawn obstacles
58 | RandomNumberGenerator rng = new RandomNumberGenerator();
59 | rng.Randomize();
60 |
61 | // Spawn an obstacle, step along the ground, and repeat as long as we're still on the ground.
62 | while (currentPos <= Texture.GetWidth())
63 | {
64 | PackedScene obstacleScene;
65 |
66 | if (_canSpawnPterodactyls)
67 | {
68 | obstacleScene = rng.RandomValue(new List<(ushort, PackedScene)>
69 | {
70 | (2, _cactus),
71 | (1, _cactusClump),
72 | (1, _cactusBaby),
73 | (2, _pterodactyl)
74 | });
75 | }
76 | else
77 | {
78 | obstacleScene = rng.RandomValue(new List<(ushort, PackedScene)>
79 | {
80 | (2, _cactus),
81 | (1, _cactusClump),
82 | (1, _cactusBaby),
83 | });
84 | }
85 |
86 | Node2D obstacle = obstacleScene.Instance();
87 | obstacle.Position = new Vector2(currentPos, obstacle.Position.y);
88 | CallDeferred("add_child", obstacle);
89 | _obstacles.Add(obstacle);
90 |
91 | int offset = rng.RandiRange(MIN_OFFSET, MAX_OFFSET);
92 | currentPos += offset;
93 | }
94 | }
95 |
96 | ///
97 | /// Teleports the ground sprite behind the other one. Called when it exits the screen.
98 | ///
99 | /// The area that represents the edge of the screen.
100 | /// Keep in mind that the colliders at the edge of the screen don't have classes, so this class uses collision
101 | /// layers to make sure that it only wraps around when it exits the screen.
102 | private void _on_Area2D_area_exited(Area2D area)
103 | {
104 | Position = Vector2.Right * _otherGround.Texture.GetWidth() + _otherGround.Position;
105 | DespawnObstacles();
106 | SpawnObstacles();
107 | }
108 |
109 | ///
110 | /// Resets the ground without messing with the position.
111 | ///
112 | private void _on_Retry_button_pressed()
113 | {
114 | DespawnObstacles();
115 | _canSpawnPterodactyls = false;
116 | SpawnObstacles(_initialObstaclePos);
117 | }
118 |
119 | ///
120 | /// Starts spawning pterodactyls.
121 | ///
122 | private void _on_Current_score_PterodactylTime()
123 | {
124 | _canSpawnPterodactyls = true;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Input buffer/demo/PressStart2P-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/PressStart2P-Regular.ttf
--------------------------------------------------------------------------------
/Input buffer/demo/Treadmill.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// Controls the ground and the obstacles that the dino runs through.
6 | ///
7 | public class Treadmill : Node2D
8 | {
9 | /// Pixels per second the ground moves at.
10 | [Export] private float _initialSpeed = 10f;
11 | /// Pixels per second per second the ground accelerates at.
12 | [Export] private float _acceleration = 0f;
13 | /// The sprites used for the ground.
14 | private Ground _ground1, _ground2; [Export] private NodePath _groundPath1 = null, _groundPath2 = null;
15 | private float _speed;
16 | private bool _moving = false;
17 | private Vector2 _initialGroundPosition;
18 |
19 | ///
20 | /// Called when the node enters the scene tree for the first time.
21 | ///
22 | public override void _Ready()
23 | {
24 | _ground1 = GetNode(_groundPath1);
25 | _ground2 = GetNode(_groundPath2);
26 |
27 | _initialGroundPosition = _ground1.Position;
28 | _speed = _initialSpeed;
29 | }
30 |
31 | ///
32 | /// Called every frame.
33 | ///
34 | /// The elapsed time since the previous frame.
35 | public override void _Process(float delta)
36 | {
37 | if (_moving)
38 | {
39 | _ground1.Position += Vector2.Left * _speed * delta;
40 | _ground2.Position += Vector2.Left * _speed * delta;
41 |
42 | _speed += _acceleration * delta;
43 | }
44 | }
45 |
46 | ///
47 | /// Called when the player presses the retry button.
48 | ///
49 | private void _on_Retry_button_pressed()
50 | {
51 | _ground1.Position = _initialGroundPosition;
52 | _ground2.Position = _initialGroundPosition + Vector2.Right * _ground1.Texture.GetWidth();
53 | _speed = _initialSpeed;
54 |
55 | Start();
56 | }
57 |
58 | ///
59 | /// Starts the treadmill? This method's inner workings are a mystery.
60 | ///
61 | private void Start() => _moving = true;
62 | ///
63 | /// ??????????
64 | ///
65 | private void _on_Dino_GotHit() => _moving = false;
66 | }
67 |
--------------------------------------------------------------------------------
/Input buffer/demo/cloud.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=2]
2 |
3 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=1]
4 | [ext_resource path="res://demo/Cloud.cs" type="Script" id=2]
5 |
6 | [sub_resource type="RectangleShape2D" id=1]
7 | extents = Vector2( 47, 13.5 )
8 |
9 | [node name="Cloud" type="Sprite"]
10 | z_index = -1
11 | texture = ExtResource( 1 )
12 | region_enabled = true
13 | region_rect = Rect2( 165.695, 0.860001, 93.9153, 29.9065 )
14 | script = ExtResource( 2 )
15 |
16 | [node name="Area2D" type="Area2D" parent="."]
17 | collision_layer = 0
18 | collision_mask = 4
19 |
20 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
21 | shape = SubResource( 1 )
22 |
23 | [connection signal="area_exited" from="Area2D" to="." method="_on_Area2D_area_exited"]
24 |
--------------------------------------------------------------------------------
/Input buffer/demo/cooler_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/cooler_icon.png
--------------------------------------------------------------------------------
/Input buffer/demo/cooler_icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="StreamTexture"
5 | path="res://.import/cooler_icon.png-630c80f169b2fa159ca034d4d89cd03b.stex"
6 | metadata={
7 | "vram_texture": false
8 | }
9 |
10 | [deps]
11 |
12 | source_file="res://demo/cooler_icon.png"
13 | dest_files=[ "res://.import/cooler_icon.png-630c80f169b2fa159ca034d4d89cd03b.stex" ]
14 |
15 | [params]
16 |
17 | compress/mode=0
18 | compress/lossy_quality=0.7
19 | compress/hdr_mode=0
20 | compress/bptc_ldr=0
21 | compress/normal_map=0
22 | flags/repeat=0
23 | flags/filter=true
24 | flags/mipmaps=false
25 | flags/anisotropic=false
26 | flags/srgb=2
27 | process/fix_alpha_border=true
28 | process/premult_alpha=false
29 | process/HDR_as_SRGB=false
30 | process/invert_color=false
31 | process/normal_map_invert_y=false
32 | stream=false
33 | size_limit=0
34 | detect_3d=true
35 | svg/scale=1.0
36 |
--------------------------------------------------------------------------------
/Input buffer/demo/default_env.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Environment" load_steps=2 format=2]
2 |
3 | [sub_resource type="ProceduralSky" id=1]
4 |
5 | [resource]
6 | background_mode = 2
7 | background_sky = SubResource( 1 )
8 |
--------------------------------------------------------------------------------
/Input buffer/demo/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/icon.png
--------------------------------------------------------------------------------
/Input buffer/demo/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="StreamTexture"
5 | path="res://.import/icon.png-a94488736806340ecddab5ffd0ccedbc.stex"
6 | metadata={
7 | "vram_texture": false
8 | }
9 |
10 | [deps]
11 |
12 | source_file="res://demo/icon.png"
13 | dest_files=[ "res://.import/icon.png-a94488736806340ecddab5ffd0ccedbc.stex" ]
14 |
15 | [params]
16 |
17 | compress/mode=0
18 | compress/lossy_quality=0.7
19 | compress/hdr_mode=0
20 | compress/bptc_ldr=0
21 | compress/normal_map=0
22 | flags/repeat=0
23 | flags/filter=true
24 | flags/mipmaps=false
25 | flags/anisotropic=false
26 | flags/srgb=2
27 | process/fix_alpha_border=true
28 | process/premult_alpha=false
29 | process/HDR_as_SRGB=false
30 | process/invert_color=false
31 | process/normal_map_invert_y=false
32 | stream=false
33 | size_limit=0
34 | detect_3d=true
35 | svg/scale=1.0
36 |
--------------------------------------------------------------------------------
/Input buffer/demo/main.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=40 format=2]
2 |
3 | [ext_resource path="res://demo/obstacles/cactus_clump.tscn" type="PackedScene" id=1]
4 | [ext_resource path="res://demo/cloud.tscn" type="PackedScene" id=2]
5 | [ext_resource path="res://demo/obstacles/cactus.tscn" type="PackedScene" id=3]
6 | [ext_resource path="res://demo/obstacles/pterodactyl.tscn" type="PackedScene" id=4]
7 | [ext_resource path="res://demo/obstacles/cactus_baby.tscn" type="PackedScene" id=5]
8 | [ext_resource path="res://demo/Treadmill.cs" type="Script" id=6]
9 | [ext_resource path="res://demo/PressStart2P-Regular.ttf" type="DynamicFontData" id=7]
10 | [ext_resource path="res://demo/sfx/die.wav" type="AudioStream" id=8]
11 | [ext_resource path="res://demo/sfx/jump.wav" type="AudioStream" id=9]
12 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=10]
13 | [ext_resource path="res://demo/sfx/point.wav" type="AudioStream" id=11]
14 | [ext_resource path="res://demo/ui/Score.cs" type="Script" id=12]
15 | [ext_resource path="res://demo/ui/HighScore.cs" type="Script" id=13]
16 | [ext_resource path="res://demo/DinoGameAnimator.cs" type="Script" id=14]
17 | [ext_resource path="res://demo/Dino.cs" type="Script" id=15]
18 | [ext_resource path="res://demo/ui/GameOverMessage.cs" type="Script" id=16]
19 | [ext_resource path="res://demo/Ground.cs" type="Script" id=17]
20 | [ext_resource path="res://demo/BufferControl.cs" type="Script" id=18]
21 |
22 | [sub_resource type="Animation" id=13]
23 | resource_name = "Intro animation"
24 | tracks/0/type = "value"
25 | tracks/0/path = NodePath("Curtain:polygon")
26 | tracks/0/interp = 2
27 | tracks/0/loop_wrap = true
28 | tracks/0/imported = false
29 | tracks/0/enabled = false
30 | tracks/0/keys = {
31 | "times": PoolRealArray( 0, 0.7 ),
32 | "transitions": PoolRealArray( 1, 1 ),
33 | "update": 0,
34 | "values": [ PoolVector2Array( -734, 0, 100, 0, 100, 600, -734, 600 ), PoolVector2Array( 0, 0, 100, 0, 100, 600, 0, 600 ) ]
35 | }
36 | tracks/1/type = "value"
37 | tracks/1/path = NodePath("Dino:position")
38 | tracks/1/interp = 1
39 | tracks/1/loop_wrap = true
40 | tracks/1/imported = false
41 | tracks/1/enabled = false
42 | tracks/1/keys = {
43 | "times": PoolRealArray( 0, 0.7 ),
44 | "transitions": PoolRealArray( 1, 1 ),
45 | "update": 0,
46 | "values": [ Vector2( 144, 220 ), Vector2( 154, 220 ) ]
47 | }
48 | tracks/2/type = "method"
49 | tracks/2/path = NodePath("Treadmill")
50 | tracks/2/interp = 1
51 | tracks/2/loop_wrap = true
52 | tracks/2/imported = false
53 | tracks/2/enabled = true
54 | tracks/2/keys = {
55 | "times": PoolRealArray( 0 ),
56 | "transitions": PoolRealArray( 1 ),
57 | "values": [ {
58 | "args": [ ],
59 | "method": "Start"
60 | } ]
61 | }
62 | tracks/3/type = "value"
63 | tracks/3/path = NodePath("Curtain:position")
64 | tracks/3/interp = 1
65 | tracks/3/loop_wrap = true
66 | tracks/3/imported = false
67 | tracks/3/enabled = true
68 | tracks/3/keys = {
69 | "times": PoolRealArray( 0, 0.7 ),
70 | "transitions": PoolRealArray( 1, 1 ),
71 | "update": 0,
72 | "values": [ Vector2( 923, 0 ), Vector2( 1750, 0 ) ]
73 | }
74 | tracks/4/type = "method"
75 | tracks/4/path = NodePath("Cloud 1")
76 | tracks/4/interp = 1
77 | tracks/4/loop_wrap = true
78 | tracks/4/imported = false
79 | tracks/4/enabled = true
80 | tracks/4/keys = {
81 | "times": PoolRealArray( 0 ),
82 | "transitions": PoolRealArray( 1 ),
83 | "values": [ {
84 | "args": [ ],
85 | "method": "Start"
86 | } ]
87 | }
88 | tracks/5/type = "method"
89 | tracks/5/path = NodePath("Cloud 2")
90 | tracks/5/interp = 1
91 | tracks/5/loop_wrap = true
92 | tracks/5/imported = false
93 | tracks/5/enabled = true
94 | tracks/5/keys = {
95 | "times": PoolRealArray( 0 ),
96 | "transitions": PoolRealArray( 1 ),
97 | "values": [ {
98 | "args": [ ],
99 | "method": "Start"
100 | } ]
101 | }
102 | tracks/6/type = "method"
103 | tracks/6/path = NodePath("Cloud 3")
104 | tracks/6/interp = 1
105 | tracks/6/loop_wrap = true
106 | tracks/6/imported = false
107 | tracks/6/enabled = true
108 | tracks/6/keys = {
109 | "times": PoolRealArray( 0 ),
110 | "transitions": PoolRealArray( 1 ),
111 | "values": [ {
112 | "args": [ ],
113 | "method": "Start"
114 | } ]
115 | }
116 | tracks/7/type = "method"
117 | tracks/7/path = NodePath("UI/Score container/Current score")
118 | tracks/7/interp = 1
119 | tracks/7/loop_wrap = true
120 | tracks/7/imported = false
121 | tracks/7/enabled = true
122 | tracks/7/keys = {
123 | "times": PoolRealArray( 0 ),
124 | "transitions": PoolRealArray( 1 ),
125 | "values": [ {
126 | "args": [ ],
127 | "method": "Start"
128 | } ]
129 | }
130 |
131 | [sub_resource type="Animation" id=15]
132 | length = 0.001
133 | tracks/0/type = "value"
134 | tracks/0/path = NodePath("Curtain:position")
135 | tracks/0/interp = 1
136 | tracks/0/loop_wrap = true
137 | tracks/0/imported = false
138 | tracks/0/enabled = true
139 | tracks/0/keys = {
140 | "times": PoolRealArray( ),
141 | "transitions": PoolRealArray( ),
142 | "update": 0,
143 | "values": [ ]
144 | }
145 |
146 | [sub_resource type="AtlasTexture" id=3]
147 | flags = 4
148 | atlas = ExtResource( 10 )
149 | region = Rect2( 1.54929, 101.546, 2400.26, 38.3509 )
150 |
151 | [sub_resource type="RectangleShape2D" id=8]
152 | extents = Vector2( 1200, 8.5 )
153 |
154 | [sub_resource type="RectangleShape2D" id=9]
155 | extents = Vector2( 49.75, 299.75 )
156 |
157 | [sub_resource type="RectangleShape2D" id=2]
158 | extents = Vector2( 24, 50 )
159 |
160 | [sub_resource type="Animation" id=18]
161 | resource_name = "Die"
162 | tracks/0/type = "value"
163 | tracks/0/path = NodePath(".:region_rect")
164 | tracks/0/interp = 1
165 | tracks/0/loop_wrap = true
166 | tracks/0/imported = false
167 | tracks/0/enabled = true
168 | tracks/0/keys = {
169 | "times": PoolRealArray( 0 ),
170 | "transitions": PoolRealArray( 1 ),
171 | "update": 0,
172 | "values": [ Rect2( 1690.2, 0, 87.8, 99.248 ) ]
173 | }
174 |
175 | [sub_resource type="RectangleShape2D" id=10]
176 | extents = Vector2( 53.316, 10 )
177 |
178 | [sub_resource type="Animation" id=11]
179 | resource_name = "Ducking"
180 | length = 0.2
181 | loop = true
182 | step = 0.05
183 | tracks/0/type = "value"
184 | tracks/0/path = NodePath(".:region_rect")
185 | tracks/0/interp = 1
186 | tracks/0/loop_wrap = true
187 | tracks/0/imported = false
188 | tracks/0/enabled = true
189 | tracks/0/keys = {
190 | "times": PoolRealArray( 0, 0.1 ),
191 | "transitions": PoolRealArray( 1, 1 ),
192 | "update": 1,
193 | "values": [ Rect2( 1866.2, 0, 120, 99.248 ), Rect2( 1984.28, 0, 120, 99.248 ) ]
194 | }
195 | tracks/1/type = "value"
196 | tracks/1/path = NodePath("../Regular hitbox/Hitbox shape:shape")
197 | tracks/1/interp = 1
198 | tracks/1/loop_wrap = true
199 | tracks/1/imported = false
200 | tracks/1/enabled = false
201 | tracks/1/keys = {
202 | "times": PoolRealArray( 0 ),
203 | "transitions": PoolRealArray( 1 ),
204 | "update": 1,
205 | "values": [ SubResource( 10 ) ]
206 | }
207 | tracks/2/type = "value"
208 | tracks/2/path = NodePath("../Regular hitbox/Hitbox shape:position")
209 | tracks/2/interp = 1
210 | tracks/2/loop_wrap = true
211 | tracks/2/imported = false
212 | tracks/2/enabled = false
213 | tracks/2/keys = {
214 | "times": PoolRealArray( 0 ),
215 | "transitions": PoolRealArray( 1 ),
216 | "update": 0,
217 | "values": [ Vector2( -1, 38 ) ]
218 | }
219 |
220 | [sub_resource type="AtlasTexture" id=6]
221 | flags = 4
222 | atlas = ExtResource( 10 )
223 | region = Rect2( 1337.9, 0, 87.8, 99.248 )
224 |
225 | [sub_resource type="Animation" id=4]
226 | resource_name = "Idle + Jump"
227 | length = 0.1
228 | tracks/0/type = "value"
229 | tracks/0/path = NodePath(".:texture")
230 | tracks/0/interp = 1
231 | tracks/0/loop_wrap = true
232 | tracks/0/imported = false
233 | tracks/0/enabled = false
234 | tracks/0/keys = {
235 | "times": PoolRealArray( 0 ),
236 | "transitions": PoolRealArray( 1 ),
237 | "update": 1,
238 | "values": [ SubResource( 6 ) ]
239 | }
240 | tracks/1/type = "value"
241 | tracks/1/path = NodePath(".:region_rect")
242 | tracks/1/interp = 1
243 | tracks/1/loop_wrap = true
244 | tracks/1/imported = false
245 | tracks/1/enabled = true
246 | tracks/1/keys = {
247 | "times": PoolRealArray( 0 ),
248 | "transitions": PoolRealArray( 1 ),
249 | "update": 0,
250 | "values": [ Rect2( 1337.9, 0, 87.8, 99.248 ) ]
251 | }
252 | tracks/2/type = "value"
253 | tracks/2/path = NodePath("../Regular hitbox/Hitbox shape:shape")
254 | tracks/2/interp = 1
255 | tracks/2/loop_wrap = true
256 | tracks/2/imported = false
257 | tracks/2/enabled = false
258 | tracks/2/keys = {
259 | "times": PoolRealArray( 0 ),
260 | "transitions": PoolRealArray( 1 ),
261 | "update": 1,
262 | "values": [ SubResource( 2 ) ]
263 | }
264 | tracks/3/type = "value"
265 | tracks/3/path = NodePath("../Regular hitbox/Hitbox shape:position")
266 | tracks/3/interp = 1
267 | tracks/3/loop_wrap = true
268 | tracks/3/imported = false
269 | tracks/3/enabled = false
270 | tracks/3/keys = {
271 | "times": PoolRealArray( 0 ),
272 | "transitions": PoolRealArray( 1 ),
273 | "update": 0,
274 | "values": [ Vector2( 0, 0 ) ]
275 | }
276 |
277 | [sub_resource type="Animation" id=12]
278 | length = 0.001
279 | tracks/0/type = "value"
280 | tracks/0/path = NodePath("../Regular hitbox/Hitbox shape:position")
281 | tracks/0/interp = 1
282 | tracks/0/loop_wrap = true
283 | tracks/0/imported = false
284 | tracks/0/enabled = true
285 | tracks/0/keys = {
286 | "times": PoolRealArray( 0 ),
287 | "transitions": PoolRealArray( 1 ),
288 | "update": 0,
289 | "values": [ Vector2( 0, 0 ) ]
290 | }
291 | tracks/1/type = "value"
292 | tracks/1/path = NodePath("../Regular hitbox/Hitbox shape:shape")
293 | tracks/1/interp = 1
294 | tracks/1/loop_wrap = true
295 | tracks/1/imported = false
296 | tracks/1/enabled = true
297 | tracks/1/keys = {
298 | "times": PoolRealArray( 0 ),
299 | "transitions": PoolRealArray( 1 ),
300 | "update": 1,
301 | "values": [ SubResource( 2 ) ]
302 | }
303 |
304 | [sub_resource type="AtlasTexture" id=1]
305 | flags = 4
306 | atlas = ExtResource( 10 )
307 | region = Rect2( 1514.3, 0, 86.632, 100 )
308 |
309 | [sub_resource type="AtlasTexture" id=7]
310 | flags = 4
311 | atlas = ExtResource( 10 )
312 | region = Rect2( 1602.4, 0, 86.632, 100 )
313 |
314 | [sub_resource type="Animation" id=5]
315 | resource_name = "Run"
316 | length = 0.2
317 | loop = true
318 | tracks/0/type = "value"
319 | tracks/0/path = NodePath(".:texture")
320 | tracks/0/interp = 1
321 | tracks/0/loop_wrap = true
322 | tracks/0/imported = false
323 | tracks/0/enabled = false
324 | tracks/0/keys = {
325 | "times": PoolRealArray( 0, 0.2 ),
326 | "transitions": PoolRealArray( 1, 1 ),
327 | "update": 1,
328 | "values": [ SubResource( 1 ), SubResource( 7 ) ]
329 | }
330 | tracks/1/type = "value"
331 | tracks/1/path = NodePath(".:region_rect")
332 | tracks/1/interp = 1
333 | tracks/1/loop_wrap = true
334 | tracks/1/imported = false
335 | tracks/1/enabled = true
336 | tracks/1/keys = {
337 | "times": PoolRealArray( 0, 0.1 ),
338 | "transitions": PoolRealArray( 1, 1 ),
339 | "update": 1,
340 | "values": [ Rect2( 1514.3, 0, 86.632, 100 ), Rect2( 1602.4, 0, 86.632, 100 ) ]
341 | }
342 |
343 | [sub_resource type="AtlasTexture" id=16]
344 | flags = 4
345 | atlas = ExtResource( 10 )
346 | region = Rect2( 952.344, 24.2983, 386.656, 33.7017 )
347 |
348 | [sub_resource type="AtlasTexture" id=19]
349 | flags = 4
350 | atlas = ExtResource( 10 )
351 | region = Rect2( 1.5369, 1.25722, 73.0468, 65.6811 )
352 |
353 | [sub_resource type="DynamicFont" id=21]
354 | size = 24
355 | font_data = ExtResource( 7 )
356 |
357 | [sub_resource type="Animation" id=22]
358 | resource_name = "Reveal"
359 | tracks/0/type = "value"
360 | tracks/0/path = NodePath("Buffer control:modulate")
361 | tracks/0/interp = 1
362 | tracks/0/loop_wrap = true
363 | tracks/0/imported = false
364 | tracks/0/enabled = true
365 | tracks/0/keys = {
366 | "times": PoolRealArray( 0, 1 ),
367 | "transitions": PoolRealArray( 1, 1 ),
368 | "update": 0,
369 | "values": [ Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ) ]
370 | }
371 |
372 | [sub_resource type="DynamicFont" id=17]
373 | size = 24
374 | font_data = ExtResource( 7 )
375 |
376 | [sub_resource type="Animation" id=20]
377 | resource_name = "Blink"
378 | length = 2.0
379 | step = 0.27
380 | tracks/0/type = "value"
381 | tracks/0/path = NodePath(".:_updating")
382 | tracks/0/interp = 1
383 | tracks/0/loop_wrap = true
384 | tracks/0/imported = false
385 | tracks/0/enabled = true
386 | tracks/0/keys = {
387 | "times": PoolRealArray( 0, 2 ),
388 | "transitions": PoolRealArray( 1, 1 ),
389 | "update": 1,
390 | "values": [ false, true ]
391 | }
392 | tracks/1/type = "value"
393 | tracks/1/path = NodePath(".:visible")
394 | tracks/1/interp = 1
395 | tracks/1/loop_wrap = true
396 | tracks/1/imported = false
397 | tracks/1/enabled = false
398 | tracks/1/keys = {
399 | "times": PoolRealArray( 0, 0.27, 0.54, 0.81, 1.08, 1.35, 1.62, 1.89, 2 ),
400 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1, 1, 1, 1 ),
401 | "update": 1,
402 | "values": [ false, true, false, true, false, true, false, true, true ]
403 | }
404 | tracks/2/type = "value"
405 | tracks/2/path = NodePath(".:modulate")
406 | tracks/2/interp = 1
407 | tracks/2/loop_wrap = true
408 | tracks/2/imported = false
409 | tracks/2/enabled = true
410 | tracks/2/keys = {
411 | "times": PoolRealArray( 0, 0.27, 0.54, 0.81, 1.08, 1.35, 1.62, 1.89, 2 ),
412 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1, 1, 1, 1 ),
413 | "update": 1,
414 | "values": [ Color( 1, 1, 1, 0 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 1 ) ]
415 | }
416 | tracks/3/type = "audio"
417 | tracks/3/path = NodePath("Milestone sound effect")
418 | tracks/3/interp = 1
419 | tracks/3/loop_wrap = true
420 | tracks/3/imported = false
421 | tracks/3/enabled = true
422 | tracks/3/keys = {
423 | "clips": [ {
424 | "end_offset": 0.0,
425 | "start_offset": 0.0,
426 | "stream": ExtResource( 11 )
427 | } ],
428 | "times": PoolRealArray( 0 )
429 | }
430 | tracks/4/type = "method"
431 | tracks/4/path = NodePath("Milestone sound effect")
432 | tracks/4/interp = 1
433 | tracks/4/loop_wrap = true
434 | tracks/4/imported = false
435 | tracks/4/enabled = false
436 | tracks/4/keys = {
437 | "times": PoolRealArray( 0 ),
438 | "transitions": PoolRealArray( 1 ),
439 | "values": [ {
440 | "args": [ 0.0 ],
441 | "method": "play"
442 | } ]
443 | }
444 |
445 | [node name="Root" type="Node"]
446 |
447 | [node name="Intro animator" type="AnimationPlayer" parent="."]
448 | "anims/Intro animation" = SubResource( 13 )
449 | anims/RESET = SubResource( 15 )
450 | script = ExtResource( 14 )
451 |
452 | [node name="Treadmill" type="Node2D" parent="."]
453 | script = ExtResource( 6 )
454 | __meta__ = {
455 | "_editor_description_": ""
456 | }
457 | _initialSpeed = 800.0
458 | _acceleration = 10.0
459 | _groundPath1 = NodePath("Ground 1")
460 | _groundPath2 = NodePath("Ground 2")
461 |
462 | [node name="Ground 1" type="Sprite" parent="Treadmill"]
463 | position = Vector2( 100, 251 )
464 | texture = SubResource( 3 )
465 | centered = false
466 | script = ExtResource( 17 )
467 | _cactus = ExtResource( 3 )
468 | _cactusClump = ExtResource( 1 )
469 | _cactusBaby = ExtResource( 5 )
470 | _pterodactyl = ExtResource( 4 )
471 | _otherGroundPath = NodePath("../Ground 2")
472 |
473 | [node name="Physics hitbox" type="StaticBody2D" parent="Treadmill/Ground 1"]
474 | position = Vector2( 1199, 28 )
475 | __meta__ = {
476 | "_editor_description_": "Used to stop the dino from falling through the ground"
477 | }
478 |
479 | [node name="Hitbox shape" type="CollisionShape2D" parent="Treadmill/Ground 1/Physics hitbox"]
480 | shape = SubResource( 8 )
481 |
482 | [node name="Orientation hitbox" type="Area2D" parent="Treadmill/Ground 1"]
483 | position = Vector2( 1199, 28 )
484 | collision_layer = 0
485 | collision_mask = 4
486 | __meta__ = {
487 | "_editor_description_": "Used to detect leaving the screen"
488 | }
489 |
490 | [node name="Hitbox shape" type="CollisionShape2D" parent="Treadmill/Ground 1/Orientation hitbox"]
491 | shape = SubResource( 8 )
492 |
493 | [node name="Ground 2" type="Sprite" parent="Treadmill"]
494 | position = Vector2( 2500, 251 )
495 | texture = SubResource( 3 )
496 | centered = false
497 | script = ExtResource( 17 )
498 | _cactus = ExtResource( 3 )
499 | _cactusClump = ExtResource( 1 )
500 | _cactusBaby = ExtResource( 5 )
501 | _pterodactyl = ExtResource( 4 )
502 | _initialObstaclePos = 400
503 | _otherGroundPath = NodePath("../Ground 1")
504 |
505 | [node name="Physics hitbox" type="StaticBody2D" parent="Treadmill/Ground 2"]
506 | position = Vector2( 1199, 28 )
507 | __meta__ = {
508 | "_editor_description_": "Used to stop the dino from falling through the ground"
509 | }
510 |
511 | [node name="Hitbox shape" type="CollisionShape2D" parent="Treadmill/Ground 2/Physics hitbox"]
512 | shape = SubResource( 8 )
513 |
514 | [node name="Orientation hitbox" type="Area2D" parent="Treadmill/Ground 2"]
515 | position = Vector2( 1199, 28 )
516 | collision_layer = 0
517 | collision_mask = 4
518 | __meta__ = {
519 | "_editor_description_": "Used to detect leaving the screen"
520 | }
521 |
522 | [node name="Hitbox shape" type="CollisionShape2D" parent="Treadmill/Ground 2/Orientation hitbox"]
523 | shape = SubResource( 8 )
524 |
525 | [node name="Cloud 1" parent="." instance=ExtResource( 2 )]
526 | position = Vector2( 401, 127 )
527 | _respawnPointPath = NodePath("../Cloud respawn position")
528 |
529 | [node name="Cloud 2" parent="." instance=ExtResource( 2 )]
530 | position = Vector2( 783, 71 )
531 | _respawnPointPath = NodePath("../Cloud respawn position")
532 |
533 | [node name="Cloud 3" parent="." instance=ExtResource( 2 )]
534 | position = Vector2( 1072, 151 )
535 | _respawnPointPath = NodePath("../Cloud respawn position")
536 |
537 | [node name="Cloud respawn position" type="Position2D" parent="."]
538 | position = Vector2( 1072, 20 )
539 |
540 | [node name="Left edge" type="Polygon2D" parent="."]
541 | polygon = PoolVector2Array( 0, 0, 100, 0, 100, 600, 0, 600 )
542 |
543 | [node name="Hitbox" type="Area2D" parent="Left edge"]
544 | collision_layer = 4
545 | collision_mask = 0
546 |
547 | [node name="Hitbox shape" type="CollisionShape2D" parent="Left edge/Hitbox"]
548 | position = Vector2( 50.25, 300.25 )
549 | shape = SubResource( 9 )
550 |
551 | [node name="Right edge" type="Polygon2D" parent="."]
552 | position = Vector2( 924, 0 )
553 | polygon = PoolVector2Array( 0, 0, 100, 0, 100, 600, 0, 600 )
554 |
555 | [node name="Dino" type="KinematicBody2D" parent="."]
556 | position = Vector2( 144, 220 )
557 | script = ExtResource( 15 )
558 | _animation_player_path = NodePath("Animator")
559 | _regularHitboxPath = NodePath("Regular hitbox/Hitbox shape")
560 | _duckingHitboxPath = NodePath("Ducking hitbox/Hitbox shape")
561 | _audioPath = NodePath("Jump sound")
562 | _regularGravity = 3600.0
563 | _shortHopGravity = 7200.0
564 | _initialJumpSpeed = 1100.0
565 |
566 | [node name="Ground hitbox" type="CollisionShape2D" parent="Dino"]
567 | shape = SubResource( 2 )
568 |
569 | [node name="Dino sprite" type="Sprite" parent="Dino"]
570 | texture = ExtResource( 10 )
571 | region_enabled = true
572 | region_rect = Rect2( 1690.2, 0, 87.8, 99.248 )
573 |
574 | [node name="Animator" type="AnimationPlayer" parent="Dino"]
575 | root_node = NodePath("../Dino sprite")
576 | autoplay = "Idle + Jump"
577 | anims/Die = SubResource( 18 )
578 | anims/Ducking = SubResource( 11 )
579 | "anims/Idle + Jump" = SubResource( 4 )
580 | anims/RESET = SubResource( 12 )
581 | anims/Run = SubResource( 5 )
582 |
583 | [node name="Regular hitbox" type="Area2D" parent="Dino"]
584 | collision_layer = 8
585 | collision_mask = 2
586 |
587 | [node name="Hitbox shape" type="CollisionShape2D" parent="Dino/Regular hitbox"]
588 | shape = SubResource( 2 )
589 |
590 | [node name="Ducking hitbox" type="Area2D" parent="Dino"]
591 | collision_layer = 8
592 | collision_mask = 2
593 |
594 | [node name="Hitbox shape" type="CollisionShape2D" parent="Dino/Ducking hitbox"]
595 | position = Vector2( 0, 40 )
596 | shape = SubResource( 10 )
597 | disabled = true
598 |
599 | [node name="Jump sound" type="AudioStreamPlayer" parent="Dino"]
600 | stream = ExtResource( 9 )
601 |
602 | [node name="UI" type="Control" parent="."]
603 | anchor_right = 1.0
604 | anchor_bottom = 1.0
605 | size_flags_horizontal = 2
606 | size_flags_vertical = 2
607 |
608 | [node name="Game over screen" type="Control" parent="UI"]
609 | visible = false
610 | anchor_right = 1.0
611 | anchor_bottom = 1.0
612 | grow_horizontal = 2
613 | grow_vertical = 2
614 | script = ExtResource( 16 )
615 | _retryButtonPath = NodePath("Retry button")
616 | _audioPath = NodePath("Death sound")
617 |
618 | [node name="Message" type="TextureRect" parent="UI/Game over screen"]
619 | anchor_right = 1.0
620 | anchor_bottom = 0.357
621 | margin_bottom = -0.200012
622 | grow_horizontal = 2
623 | grow_vertical = 2
624 | rect_pivot_offset = Vector2( 189, 13 )
625 | texture = SubResource( 16 )
626 | stretch_mode = 4
627 |
628 | [node name="Retry button" type="TextureButton" parent="UI/Game over screen"]
629 | anchor_left = 0.5
630 | anchor_top = 0.302
631 | anchor_right = 0.5
632 | anchor_bottom = 0.302
633 | margin_left = -35.0
634 | margin_top = -20.0
635 | margin_right = 35.0
636 | margin_bottom = 24.8
637 | grow_horizontal = 2
638 | grow_vertical = 2
639 | texture_normal = SubResource( 19 )
640 |
641 | [node name="Death sound" type="AudioStreamPlayer" parent="UI/Game over screen"]
642 | stream = ExtResource( 8 )
643 |
644 | [node name="Buffer control container" type="HBoxContainer" parent="UI"]
645 | margin_left = 13.0
646 | margin_top = 10.0
647 | margin_bottom = 55.0
648 |
649 | [node name="Buffer control" type="Label" parent="UI/Buffer control container"]
650 | modulate = Color( 1, 1, 1, 0 )
651 | margin_top = 10.0
652 | margin_right = 288.0
653 | margin_bottom = 34.0
654 | custom_colors/font_color = Color( 0.458824, 0.458824, 0.458824, 1 )
655 | custom_fonts/font = SubResource( 21 )
656 | text = "Buffer is ON"
657 | script = ExtResource( 18 )
658 | _animatorPath = NodePath("../Reveal animation")
659 |
660 | [node name="Reveal animation" type="AnimationPlayer" parent="UI/Buffer control container"]
661 | anims/Reveal = SubResource( 22 )
662 |
663 | [node name="Score container" type="HBoxContainer" parent="UI"]
664 | anchor_left = 1.0
665 | anchor_right = 1.0
666 | margin_left = -361.0
667 | margin_top = 10.0
668 | margin_right = -13.0
669 | margin_bottom = 55.0
670 | grow_horizontal = 0
671 |
672 | [node name="High score" type="Label" parent="UI/Score container"]
673 | margin_top = 10.0
674 | margin_right = 192.0
675 | margin_bottom = 34.0
676 | rect_pivot_offset = Vector2( -1137, 242 )
677 | size_flags_horizontal = 6
678 | custom_colors/font_color = Color( 0.458824, 0.458824, 0.458824, 1 )
679 | custom_fonts/font = SubResource( 17 )
680 | text = "HI 00000"
681 | script = ExtResource( 13 )
682 | _scoreNodePath = NodePath("../Current score")
683 |
684 | [node name="Current score" type="Label" parent="UI/Score container"]
685 | margin_left = 214.0
686 | margin_top = 10.0
687 | margin_right = 334.0
688 | margin_bottom = 34.0
689 | size_flags_horizontal = 6
690 | custom_colors/font_color = Color( 0.32549, 0.32549, 0.32549, 1 )
691 | custom_fonts/font = SubResource( 17 )
692 | text = "00000"
693 | align = 2
694 | script = ExtResource( 12 )
695 | _pterodactylMilestone = 450.0
696 | _animationPlayerPath = NodePath("Blink animation")
697 | _blinkTimerPath = NodePath("Blink timer")
698 | _pterodactylTimerPath = NodePath("Pterodactyl timer")
699 |
700 | [node name="Blink animation" type="AnimationPlayer" parent="UI/Score container/Current score"]
701 | anims/Blink = SubResource( 20 )
702 |
703 | [node name="Blink timer" type="Timer" parent="UI/Score container/Current score"]
704 |
705 | [node name="Pterodactyl timer" type="Timer" parent="UI/Score container/Current score"]
706 |
707 | [node name="Milestone sound effect" type="AudioStreamPlayer" parent="UI/Score container/Current score"]
708 | stream = ExtResource( 11 )
709 | volume_db = -15.67
710 |
711 | [node name="Curtain" type="Polygon2D" parent="."]
712 | visible = false
713 | position = Vector2( 1750, 0 )
714 | polygon = PoolVector2Array( -734, 0, 100, 0, 100, 600, -734, 600 )
715 |
716 | [connection signal="area_exited" from="Treadmill/Ground 1/Orientation hitbox" to="Treadmill/Ground 1" method="_on_Area2D_area_exited"]
717 | [connection signal="area_exited" from="Treadmill/Ground 2/Orientation hitbox" to="Treadmill/Ground 2" method="_on_Area2D_area_exited"]
718 | [connection signal="GotHit" from="Dino" to="Treadmill" method="_on_Dino_GotHit"]
719 | [connection signal="GotHit" from="Dino" to="Cloud 1" method="_on_Dino_GotHit"]
720 | [connection signal="GotHit" from="Dino" to="Cloud 2" method="_on_Dino_GotHit"]
721 | [connection signal="GotHit" from="Dino" to="Cloud 3" method="_on_Dino_GotHit"]
722 | [connection signal="GotHit" from="Dino" to="UI/Game over screen" method="_on_Dino_GotHit"]
723 | [connection signal="GotHit" from="Dino" to="UI/Score container/High score" method="_on_Dino_GotHit"]
724 | [connection signal="GotHit" from="Dino" to="UI/Score container/Current score" method="_on_Dino_GotHit"]
725 | [connection signal="IntroJumpFinished" from="Dino" to="Intro animator" method="_on_Dino_IntroJumpFinished"]
726 | [connection signal="area_entered" from="Dino/Regular hitbox" to="Dino" method="_on_Regular_hitbox_area_entered"]
727 | [connection signal="area_entered" from="Dino/Ducking hitbox" to="Dino" method="_on_Ducking_hitbox_area_entered"]
728 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="Intro animator" method="_on_Retry_button_pressed"]
729 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="Treadmill" method="_on_Retry_button_pressed"]
730 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="Treadmill/Ground 1" method="_on_Retry_button_pressed"]
731 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="Treadmill/Ground 2" method="_on_Retry_button_pressed"]
732 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="Dino" method="_on_Retry_button_pressed"]
733 | [connection signal="pressed" from="UI/Game over screen/Retry button" to="UI/Game over screen" method="_on_Retry_button_pressed"]
734 | [connection signal="BufferToggled" from="UI/Buffer control container/Buffer control" to="Dino" method="_on_Buffer_control_BufferToggled"]
735 | [connection signal="PterodactylTime" from="UI/Score container/Current score" to="Treadmill/Ground 1" method="_on_Current_score_PterodactylTime"]
736 | [connection signal="timeout" from="UI/Score container/Current score/Blink timer" to="UI/Score container/Current score" method="_on_Blink_timer_timeout"]
737 | [connection signal="timeout" from="UI/Score container/Current score/Pterodactyl timer" to="UI/Score container/Current score" method="_on_Pterodactyl_timer_timeout"]
738 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/Cactus.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using ExtensionMethods;
4 |
5 | ///
6 | /// A cactus obstacle for the dino to jump over
7 | ///
8 | public class Cactus : Node2D
9 | {
10 | // Called when the node enters the scene tree for the first time.
11 | public override void _Ready()
12 | {
13 | // Randomly choose one of the sprites
14 | RandomNumberGenerator rng = new RandomNumberGenerator();
15 | rng.Randomize();
16 | Godot.Collections.Array sprites = this.GetChildrenOfType();
17 | int spriteIndex = rng.RandiRange(0, sprites.Count - 1);
18 | for (int i = 0; i < sprites.Count; i++)
19 | {
20 | if (i != spriteIndex)
21 | {
22 | sprites[i].QueueFree();
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/Pterodactyl.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using ExtensionMethods;
4 |
5 | ///
6 | /// A flying enemy.
7 | ///
8 | public class Pterodactyl : Sprite
9 | {
10 | // Called when the node enters the scene tree for the first time.
11 | public override void _Ready()
12 | {
13 | // Fly low, medium, or high.
14 | RandomNumberGenerator rng = new RandomNumberGenerator();
15 | rng.Randomize();
16 | int weFlyHigh = rng.RandiRange(0, 2);
17 |
18 | switch (weFlyHigh)
19 | {
20 | case 0: // Ground level.
21 | Position = new Vector2(Position.x, -20);
22 | break;
23 | case 1: // Medium height, avoid by ducking.
24 | Position = new Vector2(Position.x, -80);
25 | break;
26 | case 2: // High (the dino can safely avoid this by doing nothing).
27 | Position = new Vector2(Position.x, -160);
28 | break;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/cactus.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=2]
2 |
3 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=1]
4 | [ext_resource path="res://demo/obstacles/Cactus.cs" type="Script" id=2]
5 |
6 |
7 | [sub_resource type="AtlasTexture" id=1]
8 | flags = 4
9 | atlas = ExtResource( 1 )
10 | region = Rect2( 651.247, 0.898, 50.769, 102.988 )
11 |
12 | [sub_resource type="AtlasTexture" id=2]
13 | flags = 4
14 | atlas = ExtResource( 1 )
15 | region = Rect2( 702.383, 0.898, 49.2103, 102.988 )
16 |
17 | [sub_resource type="AtlasTexture" id=3]
18 | flags = 4
19 | atlas = ExtResource( 1 )
20 | region = Rect2( 751.593, 0.898, 50.3877, 102.988 )
21 |
22 | [sub_resource type="AtlasTexture" id=4]
23 | flags = 4
24 | atlas = ExtResource( 1 )
25 | region = Rect2( 801.981, 0.898, 47.9242, 102.988 )
26 |
27 | [sub_resource type="RectangleShape2D" id=6]
28 | extents = Vector2( 19, 40 )
29 |
30 | [node name="Cactus" type="Node2D"]
31 | script = ExtResource( 2 )
32 |
33 | [node name="Sprite 1" type="Sprite" parent="."]
34 | position = Vector2( 1, -30 )
35 | texture = SubResource( 1 )
36 |
37 | [node name="Sprite 2" type="Sprite" parent="."]
38 | position = Vector2( 1, -30 )
39 | texture = SubResource( 2 )
40 |
41 | [node name="Sprite 3" type="Sprite" parent="."]
42 | position = Vector2( 1, -30 )
43 | texture = SubResource( 3 )
44 |
45 | [node name="Sprite 4" type="Sprite" parent="."]
46 | position = Vector2( 1, -30 )
47 | texture = SubResource( 4 )
48 |
49 | [node name="Area2D" type="Area2D" parent="."]
50 | position = Vector2( 1, -30 )
51 | collision_layer = 2
52 | collision_mask = 8
53 |
54 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
55 | position = Vector2( -4.76837e-07, 4 )
56 | shape = SubResource( 6 )
57 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/cactus_baby.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=9 format=2]
2 |
3 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=1]
4 | [ext_resource path="res://demo/obstacles/Cactus.cs" type="Script" id=2]
5 |
6 |
7 | [sub_resource type="AtlasTexture" id=1]
8 | flags = 4
9 | atlas = ExtResource( 1 )
10 | region = Rect2( 446.374, 0.898, 33.4, 70.8199 )
11 |
12 | [sub_resource type="RectangleShape2D" id=2]
13 | extents = Vector2( 13, 29 )
14 |
15 | [sub_resource type="AtlasTexture" id=3]
16 | flags = 4
17 | atlas = ExtResource( 1 )
18 | region = Rect2( 480.627, 0.898, 67.142, 70.8199 )
19 |
20 | [sub_resource type="RectangleShape2D" id=5]
21 | extents = Vector2( 28.5, 30.5 )
22 |
23 | [sub_resource type="AtlasTexture" id=4]
24 | flags = 4
25 | atlas = ExtResource( 1 )
26 | region = Rect2( 548.379, 0.898, 101.191, 70.8199 )
27 |
28 | [sub_resource type="RectangleShape2D" id=6]
29 | extents = Vector2( 41, 30.5 )
30 |
31 | [node name="Node2D" type="Node2D"]
32 | script = ExtResource( 2 )
33 |
34 | [node name="Sprite 1" type="Sprite" parent="."]
35 | position = Vector2( 0, -17 )
36 | texture = SubResource( 1 )
37 |
38 | [node name="Area2D" type="Area2D" parent="Sprite 1"]
39 | position = Vector2( 1, -13 )
40 | collision_layer = 2
41 | collision_mask = 8
42 |
43 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Sprite 1/Area2D"]
44 | position = Vector2( -0.999998, 18 )
45 | shape = SubResource( 2 )
46 |
47 | [node name="Sprite 2" type="Sprite" parent="."]
48 | position = Vector2( 0, -17 )
49 | texture = SubResource( 3 )
50 |
51 | [node name="Area2D" type="Area2D" parent="Sprite 2"]
52 | position = Vector2( 1, -13 )
53 | collision_layer = 2
54 | collision_mask = 8
55 |
56 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Sprite 2/Area2D"]
57 | position = Vector2( -1.5, 16.5 )
58 | shape = SubResource( 5 )
59 |
60 | [node name="Sprite 3" type="Sprite" parent="."]
61 | position = Vector2( 0, -17 )
62 | texture = SubResource( 4 )
63 |
64 | [node name="Area2D" type="Area2D" parent="Sprite 3"]
65 | position = Vector2( 1, -13 )
66 | collision_layer = 2
67 | collision_mask = 8
68 |
69 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Sprite 3/Area2D"]
70 | position = Vector2( -0.999999, 16.5 )
71 | shape = SubResource( 6 )
72 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/cactus_clump.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=2]
2 |
3 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=1]
4 |
5 |
6 | [sub_resource type="AtlasTexture" id=1]
7 | flags = 4
8 | atlas = ExtResource( 1 )
9 | region = Rect2( 802.4, 0.898, 151.583, 102.988 )
10 |
11 | [sub_resource type="RectangleShape2D" id=2]
12 | extents = Vector2( 66, 42 )
13 |
14 | [node name="Node2D" type="Node2D"]
15 |
16 | [node name="Sprite" type="Sprite" parent="."]
17 | position = Vector2( 1, -30 )
18 | texture = SubResource( 1 )
19 |
20 | [node name="Area2D" type="Area2D" parent="."]
21 | position = Vector2( 1, -30 )
22 | collision_layer = 2
23 | collision_mask = 8
24 |
25 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
26 | position = Vector2( -0.999996, 2 )
27 | shape = SubResource( 2 )
28 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/pterodactyl.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=7 format=2]
2 |
3 | [ext_resource path="res://demo/offline-sprite-2x.png" type="Texture" id=1]
4 | [ext_resource path="res://demo/obstacles/Pterodactyl.cs" type="Script" id=2]
5 |
6 |
7 | [sub_resource type="AtlasTexture" id=1]
8 | flags = 4
9 | atlas = ExtResource( 1 )
10 | region = Rect2( 260.043, -6.22105, 91.1614, 88.3723 )
11 |
12 | [sub_resource type="AtlasTexture" id=2]
13 | flags = 4
14 | atlas = ExtResource( 1 )
15 | region = Rect2( 352.66, -6.221, 91.161, 88.372 )
16 |
17 | [sub_resource type="Animation" id=3]
18 | resource_name = "Fly"
19 | length = 0.4
20 | loop = true
21 | step = 0.05
22 | tracks/0/type = "value"
23 | tracks/0/path = NodePath(".:texture")
24 | tracks/0/interp = 1
25 | tracks/0/loop_wrap = true
26 | tracks/0/imported = false
27 | tracks/0/enabled = false
28 | tracks/0/keys = {
29 | "times": PoolRealArray( 0, 0.2 ),
30 | "transitions": PoolRealArray( 1, 1 ),
31 | "update": 1,
32 | "values": [ SubResource( 1 ), SubResource( 2 ) ]
33 | }
34 | tracks/1/type = "value"
35 | tracks/1/path = NodePath(".:region_rect")
36 | tracks/1/interp = 1
37 | tracks/1/loop_wrap = true
38 | tracks/1/imported = false
39 | tracks/1/enabled = true
40 | tracks/1/keys = {
41 | "times": PoolRealArray( 0, 0.2 ),
42 | "transitions": PoolRealArray( 1, 1 ),
43 | "update": 1,
44 | "values": [ Rect2( 260.043, -6.221, 91.161, 88.372 ), Rect2( 352, -6.221, 91.161, 88.372 ) ]
45 | }
46 |
47 | [sub_resource type="RectangleShape2D" id=4]
48 | extents = Vector2( 43, 15 )
49 |
50 | [node name="Pterodactyl" type="Sprite"]
51 | position = Vector2( 0, -48 )
52 | texture = ExtResource( 1 )
53 | region_enabled = true
54 | region_rect = Rect2( 260.043, -6.221, 91.161, 88.372 )
55 | script = ExtResource( 2 )
56 |
57 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."]
58 | autoplay = "Fly"
59 | anims/Fly = SubResource( 3 )
60 |
61 | [node name="Area2D" type="Area2D" parent="."]
62 | collision_layer = 2
63 | collision_mask = 8
64 |
65 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
66 | position = Vector2( 1, 5 )
67 | shape = SubResource( 4 )
68 |
--------------------------------------------------------------------------------
/Input buffer/demo/obstacles/test_obstacle.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=2]
2 |
3 | [ext_resource path="res://demo/icon.png" type="Texture" id=1]
4 |
5 |
6 | [node name="Obstacle" type="Sprite"]
7 | texture = ExtResource( 1 )
8 |
--------------------------------------------------------------------------------
/Input buffer/demo/offline-sprite-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/offline-sprite-2x.png
--------------------------------------------------------------------------------
/Input buffer/demo/offline-sprite-2x.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="StreamTexture"
5 | path="res://.import/offline-sprite-2x.png-94e0369d38a530f011ab93dca896b32b.stex"
6 | metadata={
7 | "vram_texture": false
8 | }
9 |
10 | [deps]
11 |
12 | source_file="res://demo/offline-sprite-2x.png"
13 | dest_files=[ "res://.import/offline-sprite-2x.png-94e0369d38a530f011ab93dca896b32b.stex" ]
14 |
15 | [params]
16 |
17 | compress/mode=0
18 | compress/lossy_quality=0.7
19 | compress/hdr_mode=0
20 | compress/bptc_ldr=0
21 | compress/normal_map=0
22 | flags/repeat=0
23 | flags/filter=true
24 | flags/mipmaps=false
25 | flags/anisotropic=false
26 | flags/srgb=2
27 | process/fix_alpha_border=true
28 | process/premult_alpha=false
29 | process/HDR_as_SRGB=false
30 | process/invert_color=false
31 | process/normal_map_invert_y=false
32 | stream=false
33 | size_limit=0
34 | detect_3d=true
35 | svg/scale=1.0
36 |
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/die.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/sfx/die.wav
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/die.wav.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="wav"
4 | type="AudioStreamSample"
5 | path="res://.import/die.wav-9afca11fbf53c2118f2ff8ce19ea30b4.sample"
6 |
7 | [deps]
8 |
9 | source_file="res://demo/sfx/die.wav"
10 | dest_files=[ "res://.import/die.wav-9afca11fbf53c2118f2ff8ce19ea30b4.sample" ]
11 |
12 | [params]
13 |
14 | force/8_bit=false
15 | force/mono=false
16 | force/max_rate=false
17 | force/max_rate_hz=44100
18 | edit/trim=false
19 | edit/normalize=false
20 | edit/loop_mode=0
21 | edit/loop_begin=0
22 | edit/loop_end=-1
23 | compress/mode=0
24 |
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/jump.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/sfx/jump.wav
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/jump.wav.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="wav"
4 | type="AudioStreamSample"
5 | path="res://.import/jump.wav-de6a45369cfdfd7eb470a044e28fddb2.sample"
6 |
7 | [deps]
8 |
9 | source_file="res://demo/sfx/jump.wav"
10 | dest_files=[ "res://.import/jump.wav-de6a45369cfdfd7eb470a044e28fddb2.sample" ]
11 |
12 | [params]
13 |
14 | force/8_bit=false
15 | force/mono=false
16 | force/max_rate=false
17 | force/max_rate_hz=44100
18 | edit/trim=false
19 | edit/normalize=false
20 | edit/loop_mode=0
21 | edit/loop_begin=0
22 | edit/loop_end=-1
23 | compress/mode=0
24 |
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/point.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/Input buffer/demo/sfx/point.wav
--------------------------------------------------------------------------------
/Input buffer/demo/sfx/point.wav.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="wav"
4 | type="AudioStreamSample"
5 | path="res://.import/point.wav-f8178304fd2401078d43de013dda1f1a.sample"
6 |
7 | [deps]
8 |
9 | source_file="res://demo/sfx/point.wav"
10 | dest_files=[ "res://.import/point.wav-f8178304fd2401078d43de013dda1f1a.sample" ]
11 |
12 | [params]
13 |
14 | force/8_bit=false
15 | force/mono=false
16 | force/max_rate=false
17 | force/max_rate_hz=44100
18 | edit/trim=false
19 | edit/normalize=false
20 | edit/loop_mode=0
21 | edit/loop_begin=0
22 | edit/loop_end=-1
23 | compress/mode=0
24 |
--------------------------------------------------------------------------------
/Input buffer/demo/systems/NodeExtensions.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | namespace ExtensionMethods
5 | {
6 | ///
7 | /// Extension methods for the Node class.
8 | ///
9 | public static class NodeExtensions
10 | {
11 | ///
12 | /// Gets all of the node's children that are of the specified type
13 | ///
14 | /// The object to call this method for.
15 | /// The type to look for.
16 | /// Returns an array of references to node's children.
17 | public static Godot.Collections.Array GetChildrenOfType(this Node node) where T : Node
18 | {
19 | Godot.Collections.Array toReturn = new Godot.Collections.Array();
20 | foreach (Node child in node.GetChildren())
21 | {
22 | if (child is T)
23 | {
24 | toReturn.Add((T)child);
25 | }
26 | }
27 | return toReturn;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Input buffer/demo/systems/PauseControl.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// Pauses the game when the window loses focus.
6 | ///
7 | public class PauseControl : Node
8 | {
9 | ///
10 | /// Called by the engine to respond to engine-level callbacks.
11 | ///
12 | /// The notification that the engine has sent this node.
13 | public override void _Notification(int what)
14 | {
15 | base._Notification(what);
16 |
17 | switch (what)
18 | {
19 | case MainLoop.NotificationWmFocusOut: GetTree().Paused = true; break;
20 | case MainLoop.NotificationWmFocusIn: GetTree().Paused = false; break;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Input buffer/demo/systems/RandomNumberGeneratorExtensions.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace ExtensionMethods
7 | {
8 | ///
9 | /// Extension methods for the RandomNumberGenerator class.
10 | ///
11 | static class RandomNumberGeneratorExtensions
12 | {
13 | ///
14 | /// Executes one of the given actions based on the given probabilities.
15 | ///
16 | /// The object to call this method for.
17 | ///
18 | /// List of Tuples containing the relative probability of each action being called along with the action itself.
19 | /// For any entry (uint p, Action a), the probability of a being called is p / (sum of all p in dictionary).
20 | ///
21 | /// I chose to use ushort instead of int in order to prevent some idiot 'programmer' from the future (me) from
22 | /// accidentally including an outcome with negative probability (which would mess up this algorithm) while also
23 | /// avoiding the need for a potentially dangerous cast.
24 | public static void RandomAction(
25 | this RandomNumberGenerator rng, List<(ushort Probability, Action Action)> outcomes)
26 | {
27 | int sumOfProbabilities = 0;
28 | foreach ((ushort Probability, Action) outcome in outcomes)
29 | {
30 | sumOfProbabilities += outcome.Probability;
31 | }
32 | int choice = rng.RandiRange(1, sumOfProbabilities);
33 | uint accumulator = 0;
34 |
35 | foreach ((uint Probability, Action Action) outcome in outcomes)
36 | {
37 | accumulator += outcome.Probability;
38 | if (accumulator >= choice)
39 | {
40 | outcome.Action();
41 | break;
42 | }
43 | }
44 | }
45 |
46 | ///
47 | /// Returns a value based on the given probabilities.
48 | ///
49 | /// The object to call this method for.
50 | ///
51 | /// List of Tuples containing the relative probability of returning each value along with the value itself.
52 | /// See the description for the RandomAction method for more info.
53 | ///
54 | /// The type of the values given and the value to return.
55 | /// Random value according to the outcomes parameter.
56 | public static T RandomValue(this RandomNumberGenerator rng, List<(ushort Probability, T Value)> outcomes)
57 | {
58 | /*
59 | I made this method after realizing that RandomAction was overkill for its intended use case (i.e. selecting
60 | values!), but this one uses RandomAction internally so it works out pretty well.
61 | */
62 | T selectedValue = default(T);
63 | var actionOutcomes = new List<(ushort Probability, Action Action)>();
64 | foreach ((ushort Probability, T Value) outcome in outcomes)
65 | {
66 | actionOutcomes.Add((outcome.Probability, () => selectedValue = outcome.Value));
67 | }
68 | RandomAction(rng, actionOutcomes);
69 | return selectedValue;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Input buffer/demo/systems/StateMachine.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | ///
6 | /// Structures behaviour in terms of discrete states, each with enter, update, and exit methods.
7 | ///
8 | /// Enum representing the states that the machine can be in
9 | public class StateMachine where StateEnum : Enum
10 | {
11 | /// Dictionary mapping each state to its methods.
12 | private readonly Dictionary _behaviours;
13 | /// The current state.
14 | private StateEnum _state;
15 |
16 | ///
17 | /// Constructs the state machine containing the given behaviours.
18 | ///
19 | /// Dictionary mapping each state to its methods.
20 | public StateMachine(Dictionary behaviours, StateEnum startState)
21 | {
22 | _behaviours = behaviours;
23 | _state = startState;
24 | _behaviours[_state].Enter();
25 | }
26 |
27 | ///
28 | /// Calls the current state's Update method. Should be called in the container's _Update or _PhysicsUpdate
29 | /// method.
30 | ///
31 | /// The elapsed time since the previous frame or physics step.
32 | public void Update(float delta)
33 | {
34 | _behaviours[_state].Update(delta);
35 | }
36 |
37 | ///
38 | /// Transitions to another state.
39 | ///
40 | /// The state to transition to.
41 | public void TransitionTo(StateEnum newState)
42 | {
43 | _behaviours[_state].Exit();
44 | _state = newState;
45 | _behaviours[_state].Enter();
46 | }
47 | }
48 |
49 | ///
50 | /// Container for a state's methods (i.e. specification for the state's behaviour).
51 | /// Feel free to extend it instead of instantiating it if you want to have state-specific variables.
52 | ///
53 | public class StateSpec
54 | {
55 | /// Method to call when entering this state.
56 | public readonly Action Enter;
57 | /// Method to call repeatedly while in this state.
58 | public readonly Action Update;
59 | /// Method to call when exiting this state.
60 | public readonly Action Exit;
61 |
62 | ///
63 | /// Sets up the state's callbacks. If any callback is unspecified, it's replaced with a function that does nothing.
64 | ///
65 | /// Method to call when entering this state.
66 | /// Method to call repeatedly while in this state.
67 | /// Method to call when exiting this state.
68 | public StateSpec(Action enter = null, Action update = null, Action exit = null)
69 | {
70 | // For each callback, use the given method if it was specified, otherwise use a function that does nothing.
71 | Action noOp = () => { };
72 | Action floatNoOp = (_) => { };
73 | Enter = (enter == null) ? noOp : enter;
74 | Update = (update == null) ? floatNoOp : update;
75 | Exit = (exit == null) ? noOp : exit;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Input buffer/demo/ui/GameOverMessage.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// Pops up when the game is over to let the player know they lost
6 | ///
7 | public class GameOverMessage : Control
8 | {
9 | private TextureButton _retryButton; [Export] private NodePath _retryButtonPath = null;
10 | private AudioStreamPlayer _audio; [Export] private NodePath _audioPath = null;
11 |
12 | ///
13 | /// Called when the node enters the scene tree for the first time.
14 | ///
15 | public override void _Ready()
16 | {
17 | _retryButton = GetNode(_retryButtonPath);
18 | _audio = GetNode(_audioPath);
19 | }
20 |
21 | ///
22 | /// Appear when the dino gets hit.
23 | ///
24 | private void _on_Dino_GotHit()
25 | {
26 | Visible = true;
27 | _retryButton.CallDeferred("grab_focus");
28 | _audio.Play();
29 | }
30 |
31 | ///
32 | /// Restart the game when the button is pressed.
33 | ///
34 | private void _on_Retry_button_pressed()
35 | {
36 | GetTree().Paused = false;
37 | Visible = false;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Input buffer/demo/ui/HighScore.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | ///
6 | /// Records the best score the player has gotten.
7 | ///
8 | public class HighScore : Label
9 | {
10 | private static string SAVE_PATH = "user://dino_game_save.json";
11 | private static string HIGH_SCORE_KEY = "high_score";
12 |
13 | /// Node keeping track of the score
14 | private Score _scoreNode; [Export] private NodePath _scoreNodePath = null;
15 | private float _highScore = 0;
16 |
17 | ///
18 | /// Called when the node enters the scene tree for the first time.
19 | ///
20 | public override void _Ready()
21 | {
22 | _scoreNode = GetNode(_scoreNodePath);
23 |
24 | File saveFile = new File();
25 | if (saveFile.FileExists(SAVE_PATH))
26 | {
27 | saveFile.Open(SAVE_PATH, File.ModeFlags.Read);
28 | Godot.Collections.Dictionary saveData = new Godot.Collections.Dictionary(
29 | (Godot.Collections.Dictionary)JSON.Parse(saveFile.GetLine()).Result
30 | );
31 | float loadedScore = (float)saveData[HIGH_SCORE_KEY];
32 | Update(loadedScore);
33 | saveFile.Close();
34 | }
35 | }
36 |
37 | ///
38 | /// Called by the engine to respond to engine-level callbacks.
39 | ///
40 | /// The notification that the engine has sent this node.
41 | public override void _Notification(int what)
42 | {
43 | if (what == MainLoop.NotificationWmQuitRequest)
44 | {
45 | Dictionary saveData = new Dictionary { { HIGH_SCORE_KEY, _highScore } };
46 | File saveFile = new File();
47 | saveFile.Open(SAVE_PATH, File.ModeFlags.Write);
48 | saveFile.StoreLine(JSON.Print(saveData));
49 | saveFile.Close();
50 | }
51 | }
52 |
53 | ///
54 | /// Called when the game ends.
55 | ///
56 | private void _on_Dino_GotHit()
57 | {
58 | Update(_scoreNode.DisplayedValue);
59 | }
60 |
61 | ///
62 | /// Updates the high score.
63 | ///
64 | ///
65 | /// The score the player just got. Will be displayed if it's higher than the current high score.
66 | ///
67 | private void Update(float newScore)
68 | {
69 | if (newScore > _highScore)
70 | {
71 | _highScore = newScore;
72 | Text = "HI " + _highScore.ToString("00000");
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/Input buffer/demo/ui/Score.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 |
4 | ///
5 | /// Keeps track of the player's score.
6 | ///
7 | public class Score : Label
8 | {
9 | /// Passed to ToString to display the score.
10 | public static readonly string SCORE_FORMAT = "00000";
11 |
12 | /// Signal to emit when the player has gotten enough points to start seeing pterodactyls.
13 | [Signal] private delegate void PterodactylTime();
14 |
15 | ///
16 | /// Interface for getting the value the label is showing.
17 | ///
18 | public float DisplayedValue { get => _score; }
19 |
20 | /// How many points the player gains per second.
21 | [Export] private float _speed = 10;
22 | /// Whether changes to the score show up on screen.
23 | [Export] private bool _updating = false;
24 | /// The score display blinks whenever they player scores this many points.
25 | [Export] private float _blinkMilestone = 100;
26 | ///
27 | /// When the player earns this many points, we emit a signal that tells the ground to start spawning pterodactyls.
28 | ///
29 | [Export] private float _pterodactylMilestone = 500;
30 | /// Makes the score blink when the player reaches a milestone.
31 | private AnimationPlayer _animator; [Export] private NodePath _animationPlayerPath = null;
32 | /// Keeps track of when the score should blink.
33 | private Timer _blinkTimer; [Export] private NodePath _blinkTimerPath = null;
34 | /// Keeps track of when to emit the pterodactyl-spawning signal.
35 | private Timer _pterodactylTimer; [Export] private NodePath _pterodactylTimerPath = null;
36 |
37 | private float _score = 0;
38 | private bool _moving = false;
39 |
40 | ///
41 | /// Called when the node enters the scene tree for the first time.
42 | ///
43 | public override void _Ready()
44 | {
45 | _animator = GetNode(_animationPlayerPath);
46 | _blinkTimer = GetNode(_blinkTimerPath);
47 | _pterodactylTimer = GetNode(_pterodactylTimerPath);
48 | }
49 |
50 | ///
51 | /// Called every frame.
52 | ///
53 | /// The elapsed time since the previous frame.
54 | public override void _Process(float delta)
55 | {
56 | if (_moving)
57 | {
58 | _score += _speed * delta;
59 | }
60 |
61 | if (_updating)
62 | {
63 | // Display the score on the label.
64 | Text = _score.ToString(SCORE_FORMAT);
65 | }
66 | }
67 |
68 | /// Initializes the score.
69 | public void Start()
70 | {
71 | _moving = true;
72 | _updating = true;
73 | _score = 0;
74 | _blinkTimer.WaitTime = _blinkMilestone / _speed;
75 | _pterodactylTimer.WaitTime = _pterodactylMilestone / _speed;
76 |
77 | _blinkTimer.Start();
78 | _pterodactylTimer.Start();
79 | }
80 |
81 | ///
82 | /// Called when the game ends.
83 | ///
84 | private void _on_Dino_GotHit()
85 | {
86 | // If the score is currently blinking, stop doing that.
87 | _blinkTimer.Stop();
88 | if (_animator.PlaybackActive)
89 | {
90 | _animator.Stop();
91 | Text = _score.ToString(SCORE_FORMAT);
92 | Modulate = new Color(0xffffffff);
93 | }
94 | _moving = false;
95 | _updating = false;
96 | }
97 |
98 | ///
99 | /// Called when it's time for the score display to blink.
100 | ///
101 | private void _on_Blink_timer_timeout()
102 | {
103 | _animator.Play("Blink");
104 | _blinkTimer.Start();
105 | }
106 |
107 | ///
108 | /// Called when it's time for the ground to start spawning pterodactyls.
109 | ///
110 | private void _on_Pterodactyl_timer_timeout()
111 | {
112 | EmitSignal(nameof(PterodactylTime), new object[0]);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Input buffer/input_buffer.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | # Keeps track of recent inputs in order to make timing windows more flexible.
3 | # Intended use: Add this file to your project as an Autoload script and have other objects call the class' methods.
4 | # (more on AutoLoad: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html)
5 |
6 | # How many milliseconds ahead of time the player can make an input and have it still be recognized.
7 | # I chose the value 150 because it imitates the 9-frame buffer window in the Super Smash Bros. Ultimate game.
8 | const BUFFER_WINDOW: int = 150
9 |
10 | var keyboard_timestamps: Dictionary
11 | var joypad_timestamps: Dictionary
12 |
13 | # Called when the node enters the scene tree for the first time.
14 | func _ready() -> void:
15 | pause_mode = Node.PAUSE_MODE_PROCESS
16 |
17 | # Initialize all dictionary entris.
18 | keyboard_timestamps = {}
19 | joypad_timestamps = {}
20 |
21 |
22 | # Called whenever the player makes an input.
23 | func _input(event: InputEvent) -> void:
24 | if event is InputEventKey:
25 | if !event.pressed or event.is_echo():
26 | return
27 |
28 | var scancode: int = event.scancode
29 | keyboard_timestamps[scancode] = Time.get_ticks_msec()
30 | elif event is InputEventJoypadButton:
31 | if !event.pressed or event.is_echo():
32 | return
33 |
34 | var button_index: int = event.button_index
35 | joypad_timestamps[button_index] = Time.get_ticks_msec()
36 |
37 | # Returns whether any of the keyboard keys or joypad buttons in the given action were pressed within the buffer window.
38 | func is_action_press_buffered(action: String) -> bool:
39 | # Get the inputs associated with the action. If any one of them was pressed in the last BUFFER_WINDOW milliseconds,
40 | # the action is buffered.
41 | for event in InputMap.get_action_list(action):
42 | if event is InputEventKey:
43 | var scancode: int = event.scancode
44 | if keyboard_timestamps.has(scancode):
45 | if Time.get_ticks_msec() - keyboard_timestamps[scancode] <= BUFFER_WINDOW:
46 | # Prevent this method from returning true repeatedly and registering duplicate actions.
47 | _invalidate_action(action)
48 |
49 | return true;
50 | elif event is InputEventJoypadButton:
51 | var button_index: int = event.button_index
52 | if joypad_timestamps.has(button_index):
53 | if Time.get_ticks_msec() - joypad_timestamps[button_index] <= BUFFER_WINDOW:
54 | _invalidate_action(action)
55 | return true
56 | # If there's ever a third type of buffer-able action (mouse clicks maybe?), it'd probably be worth it to generalize
57 | # the repetitive keyboard/joypad code into something that works for any input method. Until then, by the YAGNI
58 | # principle, the repetitive stuff stays >:)
59 |
60 | return false
61 |
62 |
63 | # Records unreasonable timestamps for all the inputs in an action. Called when IsActionPressBuffered returns true, as
64 | # otherwise it would continue returning true every frame for the rest of the buffer window.
65 | func _invalidate_action(action: String) -> void:
66 | for event in InputMap.get_action_list(action):
67 | if event is InputEventKey:
68 | var scancode: int = event.scancode
69 | if keyboard_timestamps.has(scancode):
70 | keyboard_timestamps[scancode] = 0
71 | elif event is InputEventJoypadButton:
72 | var button_index: int = event.button_index
73 | if joypad_timestamps.has(button_index):
74 | joypad_timestamps[button_index] = 0
75 |
--------------------------------------------------------------------------------
/Input buffer/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=4
10 |
11 | [application]
12 |
13 | config/name="Input buffer"
14 | run/main_scene="res://demo/main.tscn"
15 | config/icon="res://demo/cooler_icon.png"
16 |
17 | [autoload]
18 |
19 | PauseControl="*res://demo/systems/PauseControl.cs"
20 | InputBuffer="*res://InputBuffer.cs"
21 |
22 | [display]
23 |
24 | window/dpi/allow_hidpi=true
25 | window/stretch/mode="2d"
26 | window/stretch/aspect="keep"
27 |
28 | [gui]
29 |
30 | theme/use_hidpi=true
31 |
32 | [input]
33 |
34 | ui_accept={
35 | "deadzone": 0.5,
36 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
37 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777222,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
38 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
39 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
40 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
41 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
42 | ]
43 | }
44 |
45 | [layer_names]
46 |
47 | 2d_physics/layer_1="Default"
48 | 2d_physics/layer_2="Obstacles"
49 | 2d_physics/layer_3="Screen edge"
50 | 2d_physics/layer_4="Dino"
51 |
52 | [mono]
53 |
54 | project/assembly_name="Input buffer"
55 |
56 | [physics]
57 |
58 | common/enable_pause_aware_picking=true
59 |
60 | [rendering]
61 |
62 | quality/driver/driver_name="GLES2"
63 | vram_compression/import_etc=true
64 | vram_compression/import_etc2=false
65 | environment/default_clear_color=Color( 1, 1, 1, 1 )
66 | environment/default_environment="res://demo/default_env.tres"
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Alex Kitt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Godot input buffer
2 |
3 | ##### More responsive input in Godot
4 |
5 | Ever had a game ignore your input because you pressed the button a millisecond too early? This project fixes that problem by implementing an input buffer, which stores inputs for a short window of time so their associated actions can be executed in the next possible frame. It's easy to add it to any project, supports keyboard and controller input, and works with both GDScript and C#. Your players won't know it's there, but they'll feel it!
6 |
7 | ## Usage
8 |
9 | ### GDScript
10 |
11 | 1. Put a copy of the `input_buffer.gd` file into your project. You can clone the repository, download a ZIP of it, or simply copy and paste the file's contents into a new file, whatever works for you. Once you have the file, move it into anywhere in your project folder.
12 | 2. Add the `input_buffer.gd` script to your project's AutoLoad settings. More information on how to do that can be found [here](https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html#autoload).
13 | 3. That's all the setup you need! To use it in your game, just call `InputBuffer.is_action_press_buffered` where you'd usually call `Input.is_action_just_pressed`
14 |
15 | ### C#
16 |
17 | Same process as the GDScript version, except the file is called `InputBuffer.cs`. Download the file, add it to your project as an AutoLoad script, and call `InputBuffer.IsActionPressBuffered` where you'd usually call `Input.IsActionJustPressed`.
18 |
19 | ## Demo
20 |
21 | 
22 |
23 | I made a replica of the Google Chrome dino game to test the input buffer (and practice a few other things, like saving & loading data and working with Godot's UI features). It's included in this repository if you want to try it out. Note that it's made in C#, so you'll need [the Mono SDK](https://www.mono-project.com/download/stable/) and [the Mono version of Godot](https://godotengine.org/download). Press the spacebar to jump and press the escape key to toggle the input buffer on and off. You may notice it's easier to quickly jump up and down when the buffer is on.
24 |
--------------------------------------------------------------------------------
/demo_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnyneverwalked/godot-input-buffer/f4680941b022f4ebe44bd3635c8658333a5c2046/demo_screenshot.png
--------------------------------------------------------------------------------