Ok, so I have a node tree in my main scene that is like this (simplified)
root
|- LevelHolder
|- Player
|- Camera2D
|- CanvasLayer
|- SomeUI
|- ...
|- Extents
|- TopLeft
|- BottomRight
And the situation I have is that my Canvas Layer has a full rect layout to stretch across the screen, and then my SomeUI element contains a bunch of UI elements that make up a HUD and a frame. I've got a Camera2D that allows me to zoom in and out on my player and level, so the editor's placement of the Node2D elements doesn't match the in-game view, since the Camera moves and zooms.
I don't want my player to be able to move underneath my HUD elements, so I've got these Extents objects in the CanvasLayer, which I can anchor to the top-left and bottom-right corners of the part of my HUD that I actually want the player to be able to walk around in.
So what I think I need to do is turn Canvas positions into positions I can set for my player.
After a lot of trial and error, this is what worked for me:
var tl = $CanvasLayer/Extents/TopLeft
var tlgp = tl.get_viewport_transform() * tl.get_global_transform()
$Player.topleft = self.get_viewport_transform().affine_inverse().xform(tlgp.origin)
var br = $CanvasLayer/Extents/BottomRight
var brgp = br.get_viewport_transform() * br.get_global_transform()
$Player.bottomright = self.get_viewport_transform().affine_inverse().xform(brgp.origin)
Which works, but seems like a lot of work, and it took me a lot of screwing around to find this all. And I can leave out self.get_global_transform() in this case because I know that root's global transform is the identity, but in general it'd be even more complicated.
So, my real question is have I just overcomplicated this? Is there some method I could use that would just do all of this for me (or something else entirely) that would let me easily place a Node2D underneath a Canvas element, no matter what size the window is, or how it's stretched or squashed?
What you are doing is correct.
Is it overkill? Perhaps. Assuming the UI does not change in way that the extends need to move, you could have the screen coordinates computed beforehand… Or compute them only when necessary.
Another thing to consider is how you are moving the Camera2D. If the camera is following the player, you can use "drag margins" that you use to specify how close the the border of the screen it has to get before the view begins to move. Although that might not be viable depending on the game.
And for another alternative, you could put the game world in a Viewport, and perhaps use a ViewportContainer which will be part of the UI. Although Viewports are a performance consideration - in particular for mobile - chances are you can afford this solution. This approach also allows to have a different resolution for the game world and the UI. See also How to make a silky smooth camera for pixelart games in Godot
This rest of this answer is confirmation of what you are doing, and hopefully making it easier to understand.
The official documentation has a formula for the screen position:
var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos)
See Viewport and canvas transforms.
First of all, we don't need to use get_global_transform() since we have property for it:
var screen_coord = get_viewport_transform() * (global_transform * local_pos)
If we know we are dealing with a Control and we want its position (i.e. Vector2.ZERO in local space) we can write a shorter version:
var screen_coord = get_viewport_transform() * rect_global_position
On the other hand, for a Node2D (and again we want its position) we can do this:
var screen_coord = get_viewport_transform() * global_position
Which is the same as:
var screen_coord = get_viewport_transform() * global_transform.origin
By the way, you get the same result like this (Which is what you are doing):
var screen_coord = (get_viewport_transform() * global_transform).origin
The difference is that here we are composing the transformation, which is extra work.
However, since we want to place a Node2D in the screen coordinates of something else, we need the inverse process.
Given this equation:
screen_coord = get_viewport_transform() * global_position
We can compose the inverse of one of these transforms at the start of both sides, like this:
get_viewport_transform().affine_inverse() * screen_coord
=
get_viewport_transform().affine_inverse() * get_viewport_transform() * global_position
And - of course - get_viewport_transform().affine_inverse() * get_viewport_transform() cancels out (to an identity transform, which we can omit), so we have:
get_viewport_transform().affine_inverse() * screen_coord = global_position
Flip it, and you have the means to position the Node2D at a given screen position:
global_position = get_viewport_transform().affine_inverse() * screen_coord
Now we can combine the computation of the screen position and how we position a Node2D at a given screen position. For example:
var screen_coords := control.get_viewport_transform() * control.rect_global_position
node2D.global_position = node2D.get_viewport_transform().affine_inverse() * screen_coords
You can use xform instead of * if you prefer. However, be aware that xform returns Variant, so you lose type information.
Related
I am trying to get my rock.png to move to the side(+x axis) when my mouse is moving. You can see that _target is the position of the event and the event is the mouse moving.
I then got the node image and set it to a variable.
In the else statement I made the rock.position the position of the _target and gave it somee space away from the _target
I want this to move because my camera moves and I want it to move with the flow of the camera
``
extends Camera2D
const zone = 10
onready var rock = get_node("rocks_png_1")
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var _target = event.position
if _target.length() < zone:
self.position = Vector2(640,350)
else:
self.position = _target.normalized() * (_target.length() - zone) * 0.5
rock.position = Vector2(int(_target)+40, int(_target)+20)
``
From the code I used above I get this error
Invalid get Index 'position' (on base: 'TextureRact')
I tried just using the same code as I used in my self.position for the camera that made it move, but when I try it for the rock.position it gives me an error that tells me I need a Vector?
Invalid get Index 'position' (on base: 'TextureRact')
The error is telling you that TextureRect does not have a position property.
What happens is that a TextureRect is a Control which are positioned differently from Node2D (Camera2D is a Node2D). You can try using rect_position with it. Although I think you would be better off using a Sprite instead of a TextureRect (Sprite is a Node2D).
Notice that your TextureRect is a child of the Camera2D in the scene tree. Well, a Node2D would move with its parent automatically. So changing it to Sprite would negate the need of the line of code you are using to move it.
… Actually, due to the way Camera2D works (it has drag and limits) you might still want do something (depending what your goal is). So, know that the get_camera_screen_center method will give you the actual visual center of the screen (which is not the target of the Camera2D, again due to drag and limits).
I do not know how to set the position of a instance in gd script. The instanced shows up on the corner of the screen.
Thanks :)
It depends on what type of instance. If it's an object that has been derived from something like a Node2D, then position.x or position.y should do it. Do you have any code we can look at?
Depends on whether you want 2D or 3D.
A common method is as such:
Create the object you want to instance in its own scene.
Save the object as a scene (.tscn file) - for example "Scene1.tscn"
Call upon your instanced scene from your main scene as follows:
Make sure to preload the scene you plan to instance before your ready() function. For example if was called "Scene1", then declare :
onready var Scene1 = preload("res://Scene1.tscn")
Then, later in your code when you want to call an instance of the scene, do it as such:
var InstancedScene = Scene1.instance()
add_child(InstancedScene)
// This simply instances the scene (I think at co-ordinates (0,0,0)), but you can tell it where to instance it using the following example:
For 3D use:
InstancedScene.transform.origin = Vector3(50.0, 0.0, -50.0)
//Gives position x=50, y=0, x=-50.
This can also be used for rotation, just specify transform.basis and give a similar Vector3 coordinate set.
OR for 2D use:
InstancedScene.position = Vector2(100.0, 100.0)
//This instances the object 100 pixels across and 100 pixels down. Sub in whatever values you need. You can also specify a rotation using InstancedScene.rotation_degrees = 45 (To give 45 degrees for example)
I created a new path2d following the instructions in the my first game article: http://docs.godotengine.org/en/3.0/getting_started/step_by_step/your_first_game.html
I wanted to move the "box" on screen so that I could see how the mobs spawn in, but when I ran the scene, it stayed off screen.
I created a new path2d, centered this one in the middle of the screen, and it works like I wanted it to, but now I moving this one in the editor doesn't update the position in game.
What's going on?
Thanks
func _on_mobtimer_timeout():
$mobtimer.wait_time = 0.1 + randf() / 2
$mobspawn/moblocation.set_offset(randi())
var mob = Mob.instance()
add_child(mob)
var direction = $mobspawn/moblocation.rotation + PI/2
mob.position = $mobspawn/moblocation.position
direction += rand_range(-PI/8, PI/8)
mob.rotation = direction
mob.set_linear_velocity(Vector2(rand_range(200, 200 + score * 30), 0).rotated(direction))
A Node2D's position property is relative to it's parent's position. The code from the Dodge The Creeps tutorial assumes that MobPath is located at 0, 0 and fails when that assumption is false.
In your case you are taking a MobSpawnLocation's position relative to MobPath and then setting it as the new Mob's global position.
Luckily Node2D's have another property that we can use in these circumstances global_position. It can be used like this:
mob.position = $mobspawn/moblocation.global_position
http://docs.godotengine.org/en/stable/classes/class_node2d.html#member-variables
This isn't a full solution, but I found a weird workaround. Instead of changing the position in the editor, if you use the nodes on the orange box (at the intersection of orange and blue), you can kind of alternate to move the box around.
How would I go about changing the registration point of a movieclip labeled "tablet" placed on the stage (root) dynamically?
For example: I have the following:
var tablet = this.tablet; //movieclip labeled "tablet" on stage
function resetTablet(){
tablet.regX = tablet.width/2; //move registration point to center
tablet.regY = tablet.height/2;
}
However when I call it using GSAP:
var tl = new TimelineLite();
tl.to(tablet, 1, {alpha:1, onComplete:resetTablet})
.to(tablet, 3, {alpha:0, scaleX:1.5, scaleY:1.5})
the registration point is still set to the upper left corner rather than the center.
What am I doing wrong here? Thanks!
Registration points affect change both the transformation point, but also the position. If you set a displayobject that is 100x100 pixels to regX=50;regY=50, then it will draw from that point, moving the content 50px to the top and left. If you make that change you should also translate the clip to x=50;y=50.
An issue with you example is that there is no width or height on EaselJS content (explained here). You can get the bounds of anything generated by Flash CC using the nominalBounds property, which Flash exports as a property on every object. If you have multiple frames, you can turn on "multi-frame bounds" in the publish settings, and a frameBounds property is added to the objects as well.
Note that nominalBounds and frameBounds are not used by the getBounds method.
Here is how you might be able to approach it.
var bounds = tablet.nominalBounds;
tablet.regX = bounds.width/2;
tablet.regY = bounds.height/2;
// Optional if your actual registration point was [0,0] before:
tablet.x += tablet.regX;
tablet.y += tablet.regX;
Hope that helps.
I'd like to create choropleth map of Czech Republic. Inspired by this article http://bl.ocks.org/mbostock/4060606, I have created this
http://jsfiddle.net/1duds8tz/2/
var width = 960;
var height = 500;
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
var offset = [width / 2, height / 2];
var projection = d3.geo.mercator().scale(6000).center([15.474, 49.822]).translate(offset);
var path = d3.geo.path().projection(projection);
queue().defer(d3.json, "..map.geojson").await(ready);
function ready(error, reg) {
var group = svg.selectAll("g").data(reg.features).enter().append("g");
group.append("path").attr("d", path).attr("fill", "none").attr("stroke", "#222");
}
When I tried to fill svg path with some color, I ended on this
http://jsfiddle.net/1duds8tz/3/
group.append("path").attr("d", path).attr("fill", "red").attr("stroke", "#222");
There are odd values in path d attribute.
My GeoJSON data must be somehow faulty but I can't figure what is wrong.
Everything looks right here: https://gist.github.com/anonymous/4e51227dd83be8c2311d
Your geoJSON is corrupted and as a result your polygons are being drawn as the interiors of an infinitely bounded polygon. That's why when you attempt to give a fill to the path, it goes beyond the extent of the screen but still displays the border just fine. I tried to reverse the winding order of your coordinates array, and that seemed to fix all of them except for "Brno-venkov", which might be the source of your problems (especially given its administrative shape).
I'd suggest going back to where you created the original GeoJSON and try to re-export it with simplification. If you want to reverse the coordinates on your GeoJSON to correct the winding order, that's pretty simple:
geodata = d3.selectAll("path").data();
for (x in geodata) {geodata[x].geometry.coordinates[0] = geodata[x].geometry.coordinates[0].reverse()}
But this won't fix the problem polygon, nor will not reversing its coordinates.
In case you are familiar with svg manipulation you can try geojson2svg. This allows you manipulate svg in standard way but you have to code a little more. In case your application requires d3 for many other purpose then d3 is best solution.
I've got exactly the same problem with Mapzen's .geojson files.
.reverse()-ing isn't good enough, if you can't make sure all your data has the same winding order.
I solved it with this one:
https://www.npmjs.com/package/geojson-rewind
You'll need to have npm & require available
Install it, and save it to your project
npm i -g geojson-rewind
Import it, to make it useable
var rewind = require('geojson-rewind');
Use it on the data, in this case:
req = rewind(req);
Tip: If you are working with static data, you can do this only once on the console, and you're good to go.