How to increase the score in a 3D platformer in Godot? - game-engine

I am following along with the "Your first 3D game" tutorial on Godot Docs. I am making slight alterations to make my own game (a Crash Bandicoot remake). I'm currently on the "Score and replay" chapter.
The main difference is that my obstacles are already in the scene, whereas the tutorial has obstacles that are randomly generated.
Here is the script attached to my level scene:
extends Node
export (PackedScene) var obstacle_scene
func _start():
var obstacle = obstacle_scene.instance()
obstacle.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
Note that in the editor my obstacle_scene is hooked up to my Obstacle.tscn.
And here is the code attached to UserInterface/ScoreLabel:
extends Label
var score = 0
func _on_Obstacle_squashed():
score += 1
text = "Score: %s" % score
Expected result:
I want to increase the score every time I jump on a cube that is already in the scene. Unlike the tutorial, I am not randomly generating obstacles (or "mobs" as they're called in the tutorial).
Actual result:
I can successfully run the game without errors, but jumping on obstacles does not change the score.
I hope I gave enough information but happy to share more code if necessary.

Is the squashed signal ever being called?
If not, the _on_Obstacle_Squashed() function will never be called.

I believe the function _start is not running at all, since you didn't share any code that calls it, I can't find it in the linked tutorial… And you probably wanted to use _ready.
There is another issue: the code in _start instantiates the scene (creates a new instance), and connects it. But this new instance is never added to the scene.
To be clear, there can be multiple instances of a scene. For example, some could be added in the editor, and others created from code. And each one of those instances can emit signals. And you need to have those signals connected if you want to use them.
If you have added instances from the editor, you can connect their signals from the editor too. Go to the Node panel on the Signals tab, double click the signal you want and Godot will bring a window where you can specify where to connect the signal to.
It is also possible to connect from code the signals of instances created on the editor… However, I reiterate, that is not what you are doing. You are creating a new instance by calling the instance method. This new instance is distinct from any instance added from the editor. Instead you would get the instances form the scene tree, for example with get_node or the $ syntax.
For example:
func _ready() -> void:
$obstacle.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
And you would have to do that for each instance:
func _ready() -> void:
$obstacle.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
$obstacle2.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
$obstacle3.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
# …
On the other hand, if you created the instances from code, then you can't connect them from the editor and your only option is to connect them code. As the tutorial does. So, we can take an example form the tutorial:
var mob = mob_scene.instance()
# …
add_child(mob)
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
As you can see, it creates a new instance with the instance method. Then adds it to the scene tree as a child of the Node where this script is running by using the add_child method. And finally it connect the signal.
Anyway, I would like to push you into another direction: You can create a signal bus (event bus). Which is a common pattern in Godot projects.
The insight is that an object can make another object emit their signals.
What you do is create a new script, extending Node, and just with the signals you want. For example:
extends Node
signal squashed
And not make it an autoload. To do that you to project settings on the AutoLoad tab, then select your script and give it a name. I'll call it SignalBus.
Now, in your code you can emit that signal, for example:
func squash():
SignalBus.emit_signal("squashed")
queue_free()
And you can connect and handle that signal:
func _ready() -> void:
SignalBus.connect("squashed", self, "_on_squashed")
func _on_squashed() -> void:
prints("Something")
Or, in your case:
func _ready() -> void:
SignalBus.connect("squashed", $UserInterface/ScoreLabel, "_on_Obstacle_squashed")
The trick here is that the cubes don't have the "squashed" signal, the SignalBus has it, and the cubes tell the SignalBus to emit it. So you don't have to connect the instances of your scene (which, again, don't have the signal), but you only need to connect SignalBus, because it is what is actually emitting signals.

Related

Is there such thing as a global variable? (GDscript)

I'm trying to make a game but I need something like a global variable for it to work. Is there a way in GDscript that you can make a variable that works across all nodes with scripts. I need a button that makes you buy a gun which would set a variable to true and with that variable being true, you could equip the gun.
This question doesn't really fit this section where it says: What did you try and what were you expecting? so I'm just gonna skip it.
Is there a way in GDscript that you can make a variable that works across all nodes with scripts.
You could use an autoload.
Go to the Project menu, the Project Settings option, in the Autoloads tab… And there you can set a script, and give it a name. It will be available for every node in the scene tree.
For example, you can have a "globals.gd" script that looks like this:
extends Node
var has_gun := false
Then you make it an autoload with the name "Globals". So in any other script you can use it. For example:
extends Node
func _ready() -> void:
print(Globals.has_gun)
And yes, autoloads will stay there even if you change the current scene.
You might also be interested in the Signal bus (Event bus) pattern, which involves defining signals in an autoload, such that other scripts emit them or connect to them.
Technically the autoload is not a true global. As I said, the autoload will be available for the nodes in the scene tree.
This also means the autoload will not be available for a script that is not a Node (e.g. a Resource), or otherwise not in the scene tree.
Unless you use a hacky workaround to get a reference to the autoload, which I will not go into.
Instead, I will give you an alternative: resource based communication.
This time you create a "global_resource.gd" that look like this:
class_name GlobalResource
extends Resource
var has_gun := false
And then you can use the context menu on the FileSystem panel to create a new resource. When Godot asks you for the type, you select GlobalResource, and give it a name such as "globals.tres".
Then you can use it like this:
extends Node
const Globals := preload("res://globals.tres")
func _ready() -> void:
print(Globals.has_gun)
Everywhere you preload that same resource file ("globals.tres") you will get the same Resource instance. It does not matter if it is in the same scene, or if you changed the current scene.
And yes, this does not depend on the scene tree. And yes, you can put signals in there too.
For completeness sake, I'll also mention that there is another hacky workaround which involves defining a Dictionary or Array as const in a script with a class name. Since in Godot 3 const does not imply inmutable (don't count on this on Godot 4). That is how some people currently work around the lack of static variables. However, I believe you won't find it necessary.

Class design for keeping track of visited nodes

I am stuck at trying to decide something in my class design. This is the overview of what I need to design:
Robot that paints blocks on a square grid (a grid is collection of n blocks).
Let's say there are 4 types of blocks in a grid and each block could cost different amount to paint.
User will input command to instruct robot which block to paint.
If block was already painted, there will be penalty to repaint it.
Finally, report total cost (split by block types), and commands issued by the user.
Some of the things that I have modeled are as follows:
class Robot {
// get current location
// place it at given location
}
class Grid {
// Grid is a collection of blocks
Block[][] blocks;
}
class Block {
// each block has it's coordinates,
// has paint status (painted or unpainted),
// and accepts a visitor to determine price to paint
}
class SquareBlock extends Block {
}
class RectangularBlock extends Block {
}
For issuing commands, I modeled them as Command design pattern.
Question:
I am getting confused on is in which class should the visited (aka painted) blocks be stored (to handle #4 and #5 above)?
Should I store them in Robot? (I didn't thing it belonged in Robot because it felt like tight coupling between the notion of Robot and paintable block.)
I didn't want to store it in Grid as well because, again, I don't think that Grid needs to know what action is being taken on it (not too sure about this though).
I could've stored in different class (say Foo) but then I thought that may be user can issue a command like where ever Robot is, paint next 2 blocks. In this case, since this would be executed in PaintCommand (handled by CommandPattern), Foo wouldn't know what blocks have been painted.
Please let me know your thoughts.

iOS9 storyboard what is unhandled action (handleNonLaunchSpecificActions)?

I've noticed the following error popping up in the console when running my app on iOS 9 when using a storyboard. I'm using xCode7. Is this something I need to be concerned about?
-[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:] ** unhandled action -> <FBSSceneSnapshotAction: 0x176bfb20> {
handler = remote;
info = <BSSettings: 0x176a5d90> {
(1) = 5;
};
}
There is nothing wrong with your code. This is a logging message internal to Apple, and you should file a radar about it.
There are two hints that show that this is probably Apple's code:
The underscore leading the method name _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion is a convention indicating that the method is private/internal to the class that it's declared in. (See this comment.)
It's reasonable to guess that the two letter prefix in FBSSceneSnapshotAction is shorthand for FrontBoard, which according to Rene Ritchie in "iOS 9 wish-list: Guest Mode" is part of the whole family of software related to launching apps:
With iOS 8, Apple refactored its system manager, SpringBoard, into several smaller, more focused components. In addition to BackBoard, which was already spun off to handle background tasks, they added Frontboard for foreground tasks. They also added PreBoard to handle the Lock screen under secure, encrypted conditions. [...]
I have no idea what the BS prefix in BSSettings is for, but
BS is shorthand for BackBoard Settings, and an analysis of this log message would indicate that it's not anything you did, and you should file a radar with steps to reproduce the logging message.
If you want to try and grab a stack trace, you can implement the category linked to here. Some would argue that overriding private API is a bad idea, but in this case a temporary injection to grab a stack trace can't be too harmful.
EDIT:
But, we still want to know what this action is. So I put a breakpoint on -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion] and started printing out register values and found a class called FBSceneImpl which had a whole bunch of information about my application:
We are able to find out which private method is called next (stored in the program counter, instruction pointer, register 15.)
I tried finding the un-handled FBSceneSnapshotAction referenced in the log, but no dice. Then, I subclassed UIApplication, and overrode _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion. Now I was able to get at the action directly, but still, we don't know what it is.
Then, I looked at the FBSceneSnapshotAction again. Turns out it has a superclass called BSAction.
Then I wrote a tool similar to RuntimeBrowser and looked up all of the subclasses of BSAction. It turns out that there's quite a list of them:
The two method names we have (one from the log and one from the program counter on the devices) indicate that these actions are used under the hood for passing actions around the system.
Some actions are probably sent up to the app delegate's callbacks, while others are handled internally.
What's happening here is that there is an action that wasn't handled correctly and the system is noting it. We weren't supposed to see it, apparently.
AFAIK, the info above is related to iOS during snapshot the screen (i suppose for double click home multitask related behaviour).I deeply investigated my application and seems that it does not get any side behaviours. You can safely ignore it, for now.
You can use the following gist simple category to test yourself against the calls to the above function:
I have figured it out, it will happen when you have IBAction method declared in .h or .m file but you have not bind it to any control.
.m example:
- (IBAction)click:(id)sender{
}
but not assigned this method to any control in storyboard.
haven't find out why it happens in my app, but at least you can catch the exception, if you want to keep this from popping up in your log pane. It's not a solution, but it might give you more insight why it is happing by inspecting any of the arguments that are passed in the catch.
swift 2 version:
import UIKit
extension UIApplication {
func _handleNonLaunchSpecificActions(arg1: AnyObject, forScene arg2: AnyObject, withTransitionContext arg3: AnyObject, completion completionHandler: () -> Void) {
//whatever you want to do in this catch
print("handleNonLaunchSpecificActions catched")
}
}

How to change variables between scripts in UnityScript?

I made a game have two objects in the hierarchy panel. One called GameScreen and another called Clock I set up. Each has its own script attached. GameScreen uses game.js and the other uses clock.js.
When I win the game a popup box appears and says, "You've won!" This is caused by the game.js script. However, the clock.js is still running, so my clock is still counting down. When the time is up the clock.js makes a popup box saying, "you lose!"
This causes for a "you win" screen to pop up when you win and then later a you lose screen to appear. As you can probably guess, this is super annoying. If there was a way I could change variables in one script from another,, I could get the clock to stop when you won or I could get the game to stop when the time ran out.
Is there some way to do this??
For example here are two javascript files one on clock and the other on GameScreen . I want the first one to change the variable changeMe in the second to two.
1.js:
function start(){
}
2.js:
var changeMe:int;
When you win the game, you can change clock's variable from game.js this way:
GameObject.Find("Clock").GetComponent("clock.js").variable_name = new_value;
, where [variable_name] and [new_value] obviously depend on your project. variable_name has to be an exposed variable.
You can simply use SendMessage() it will make you able to call a function from another object easily.Basically you need to write a function to destroy the clock after wining the game,let's say your function is called destroyClock so you should add that to your game.js script:
gameobject.Find("here goes the name of your clock obj").sendMessage("destroyClock")

Eclipse call ViewPart saveState on View close

I have a Eclipse plugin that uses a view which extends ViewPart. ViewPart has a saveState method which requires an IMemento.
I added my code to saveState and the corresponding init method and it works. Unfortunately, saveState is only called if the entire workspace is shutting down. My view is not of such great importance that I can expect it to be opened the entire time. Hence, it would be cool if saveState would be called on view closure.
I found a view-part listener as mean to react on view closure, but what I do not get is where the IMemento comes from. Where do I get the memento object that is used on workspace closure? Or where do I have to store my own memento object to make the view part use it in the init method if the view is (re)opened?
#Override
public void saveState(IMemento memento) {
super.saveState(memento);
memento = memento.createChild(MEMENTO_GUI_STATE);
memento.putBoolean(MEMENTO_IS_FLAT, !isHierarchicalModeActive());
memento.putBoolean(MEMENTO_IS_CATEGORY_MODE_ACTIVE, comboViewer.isVisible());
}
This is my saveState - can I tell my view somehow tell to call it every time the view closes?
Take a look at this question in the Eclipse FAQ:
Storing view state is done in two commons ways, depending on whether
you want to store settings between workbench sessions or across
invocations of your view. The first of these facilities is found
directly on IViewPart. When the workbench is shut down, the method
saveState is called on all open views.
Another mechanism for persisting view state is the JFace
IDialogSettings facility. The advantage of dialog settings over the
view save/init mechanism is that you can control when settings are
persisted. The saveState method is called only if your view is open
when the workbench shuts down, so it is not useful for storing view
state when the view is closed by the user. Dialog settings, on the
other hand, can be changed and persisted whenever you want.
Go to this other question or to the Eclipse documentation itself for the settings mechanism.
Well this could be "a bit" ugly but nothing else came to my mind: store memento variable as a field variable, initialize it in your init(IViewSite site, IMemento memento) method, override dispose() and call saveState(IMemento memento) explicitely.
You can read and write your own XMLMemento from your org.eclipse.core.runtime.Plugin.getStateLocation() at any time you want. As #BelaViser mentioned, you could write your file in your IViewPart#dispose() method and read it in your view constructor.