I have a few square-shaped nodes (like floor tiles) going along the screen and I'd like to restrict my player (P) node to moving within these nodes.
---------------------------------
| | P | | | | | | | <- Want no movement allowed
--------------------------------- outside of these squares.
| |
-------------
| | | | ...
-------------
I'm wondering if there's an elegant way to do this with SpriteKit Physics, that doesn't involve putting invisible blocks all the way around the floor.
Thanks!
An SKConstraint object describes a mathematical constraint on a node’s position or orientation.
You can use SKConstraint to keep a node a certain distance from a specific point in horizontal axis:
let center = size.width/2.0, difference = CGFloat(170.0)
let leftConstraint = SKConstraint.positionX(SKRange(constantValue: center - difference))
let rightConstraint = SKConstraint.positionX(SKRange(constantValue: center + difference))
player.constraints = [leftConstraint, rightConstraint]
You can also decide to enable or disable a certain constraint during the game:
leftConstraint.enabled = false
You use edge based physics bodies, not volume based physics bodies. So in your construction of the physics body, look for anything with edge in the constructor. Now, if you want to be able to walk between the tiles, you will have to create 1 physics body for the outer wall of your floor, because doing it tile by tile will mean you will be stuck in individual tiles.
Related
I have a plane with scale (64,1,36) and rotation is (90,-180,0) and need the local coordinate of a raycast hit in the 2d coordinates format:
(0,0)-------(64,0)
| |
| |
| |
(0,36)------(64,36)
with my current code:
RaycastHit hit;
Vector3 coords = new Vector3();
if (Physics.Raycast(GazeOriginCombinedLocal, GazeDirectionCombined, out hit, Mathf.Infinity))
{
if (!hit.transform.Equals("Cube"))
{
Pointer.transform.position = hit.point; //Green cube for visualization of hit in worldspace
// coords = RectTransformUtility.ScreenPointToLocalPointInRectangle(Plane, hit.point, Camera.main, out coords);// no result at all
}
}
Trying this:
hit.transform.InverseTransformPoint(hit.point)
gives me this
(5,-5)---(-5,-5)
| |
| (0,0) |
| |
(5,5)----(-5,5)
Does some have an idea to get the needed format?
Thats how my plane which is a child of the main camera and my hierarchy looks like:
Thanks in advance
I think you could use the Transform.InverseTransformPoint which
Transforms position from world space to local space.
And then since this also is affected by the scale multiple it again by the scale of the plane using Vector3.Scale.
So your coords should probably be something like
coords = hit.transform.localScale / 2f + Vector3.Scale(hit.transform.InverseTransformPoint(hit.point), hit.transform.localScale);
can't test it right now though since typing on smartphone. You might e.g. need to invert the y/z component according to your needs and depending how the plane is rotated etc. But I hope this gives you an idea
In order to debug what's wrong you should probably print out the values step by step
var scale = hit.transform.localScale; // 64, 1, 36
var halfScale = scale / 2f; // 32, 0.5, 18
var localHitPoint = hit.transform.InverseTransformPoint(hit.point);
Debug.Log($"{nameof(localHitPoint)}:{localHitPoint:0.000}");
So what I had expected originally here would be values like
(-0.5, 0.5, 0)----(0.5, 0.5, 0)
| |
| (0, 0, 0) |
| |
(-0.5, -0.5, 0)---(0.5, -0.5, 0)
BUT as you now added: Your plane is rotated!
The 90° on X actually makes that Y and Z switch places. So in order to get the desired Y coordinate you would rather read the localHitPoint.z.
Then the 180° on Y basically inverts both X and Z.
So I would now expect the values to look like
(0.5, 0, -0.5)----(-0.5, 0, -0.5)
| |
| (0, 0, 0) |
| |
(0.5, 0, 0.5)---(-0.5, 0, 0.5)
Which looks pretty much like the values you describe you are getting. Not sure though why you have a factor of 10 and why you didn't need to switch Y and Z.
However since you actually want the 0,0 to be in the top-left corner you only need to flip the X axis and use Z instead of Y so
fixedLocalHitPoint = new Vector2(-localHitPoint.x, localHitPoint.z);
Debug.Log($"{nameof(fixedLocalHitPoint)}:{fixedLocalHitPoint:0.000}");
Which should now give you values like
(-0.5, -0.5)----(0.5, -0.5)
| |
| (0, 0) |
| |
(-0.5, 0.5)----(0.5, 0.5)
And still you need to scale it up again
var scaledHitPoint = Vector2.Scale(fixedLocalHitPoint, new Vector2 (scale.x, scale.z));
Debug.Log($"{nameof(scaledHitPoint)}:{scaledHitPoint:0.000}");
Which should now give values like
(-32, -18)----(32, -18)
| |
| (0, 0) |
| |
(-32, 18)-----(32, 18)
That's why you need to add the center point as a reference
coords = new Vector2(halfScale.x, halfScale.z) + scaledHitPoint;
Debug.Log($"{nameof(coords)}:{coords:0.000}");
Which now should be
(0, 0)------(64, 0)
| |
| (32, 18) |
| |
(0, 36)-----(64, 36)
I hope this brings a bit more light into where these "strange" values come from.
Since your camera is scaled 1,1,1 and there is nothing else involved I have a hard time finding where the factor of 10 would have sneaked its way into the calculation to be honest.
If you want to convert this:
hit.transform.InverseTransformPoint(hit.point)
which gives this:
(5,-5)---(-5,-5)
| |
| (0,0) |
| |
(5,5)----(-5,5)
to this:
(0,0)-------(64,0)
| |
| |
| |
(0,36)------(64,36)
Why not do this:
Vector2.Scale(
hit.transform.InverseTransformPoint(hit.point) - new Vector2(5,-5),
new Vector2(-6.4, 3.6)
);
This answer hardcodes the (5,-5) and (-6.4, 3.6) terms because the question doesn't include enough information to use variables instead.
Assuming the scale of the parent of the plane (Main Camera) is (10,10), then this should suffice:
Vector3 planeScale = hit.transform.localScale;
Vector3 cameraScale = hit.transform.parent.localScale;
result = Vector2.Scale(
hit.transform.InverseTransformPoint(hit.point)
- new Vector2(cameraScale * 0.5f ,-cameraScale * 0.5f),
new Vector2(-planeScale.x * 0.5f/cameraScale.x, planeScale.y * 0.5f / cameraScale.y)
);
I have a terrain generated in my ROBLOX game (hills, flats, rivers) and I have a model of a tree. Tree is just a group of "bricks" so it looks like kind-of a Minecraft tree.
Now I want to clone that tree programatically at run-time and I need to place that copy (clone) somewhere else on the map.
All of this is easy. But after I give my clone new coordinates (I keep coordinate for Y axis the same like my original tree), it's placed or in the air, or it is under the surface of my terrain.
How do I place it directly ON the terrain?
Or how do I get the Y-axis coordinate of that particular place on map?
Thanks!
EDIT
While answer from #Kylaaa is perfectly complete, here I include my solution, while I needed also an information about the material of the terrain in that place (rocks / grass / etc.)
function FindTerrainHeight(pos)
local VOXEL_SIZE = 4
local voxelPos = workspace.Terrain:WorldToCell(pos)
// my region will start at -100 under the desired position and end in +100 above desired position
local voxelRegion = Region3.new((voxelPos - Vector3.new(0,100,0)) * VOXEL_SIZE, (voxelPos + Vector3.new(1,100,1)) * VOXEL_SIZE)
local materialMap, occupancyMap = workspace.Terrain:ReadVoxels(voxelRegion, VOXEL_SIZE)
local steps = materialMap.Size.Y // this is actually 200 = height of the region
// now going from the very top of the region downwards and checking the terrain material
// (while it's AIR = no terrain found yet)
for i = steps, 1, -1 do
if (materialMap[1][i][1] ~= Enum.Material.Air) then
materialMap[1][i][1] ---> this is the material found on surface
((i-100) * VOXEL_SIZE) ---> this is the Y coordinate of the surface in world coordinates
end
end
return false
end
EDIT 2
So it turns out that #Kylaaa's answer solves everything. It includes also terrain material! As 4th item in the tuple. It means my solution is too complicated with no reason. Use his solution.
Try raycasting downward from your starting point until you find the terrain. game.Workspace:FindPartOnRayWithWhitelist() is great for this!
-- choose some starting point in world space
local xPos = 15
local yPos = 20
local zPos = 30
-- make a ray and point it downward.
-- NOTE - It's unusual, but you must specify a magnitude for the ray
local origin = Vector3.new(xPos, yPos, zPos)
local direction = Vector3.new(0, -100000, 0)
local ray = Ray.new(origin, direction)
-- search for the collision point with the terrain
local whitelist = { game.Workspace.Terrain }
local ignoreWater = true
local _, intersection, surfaceNormal, surfaceMaterial = game.Workspace:FindPartOnRayWithWhitelist(ray, whitelist, ignoreWater)
-- spawn a part at the intersection.
local p = Instance.new("Part")
p.Anchored = true
p.Size = Vector3.new(1, 1, 1)
p.Position = intersection -- <---- this Vector3 is the intersection point with the terrain
p.Parent = game.Workspace
If your model is moving around in unusual ways when you call SetPrimaryPartCFrame(), be aware that Roblox physics does not like interpenetrating objects. So models will often get pushed upwards until they are no longer interpenetrating. Objects that are CanCollide = false will not have this issue, but they will also fall through of the world if they are not Anchored or welded to a part is set to CanCollide = true.
Simple setup - I have bouncing ball nodes (blue) and static ball nodes (green). The grey box has Physics body SKPhysicsBody(edgeLoopFrom: box.frame), so it limits the bouncing ball from moving outside. I set the bouncing balls node (blue) velocity to a random CGVector upon creation and it starts moving. It bounces off the static ball node (green), and off the edges of the grey box.
My general SKPhysicsBody setup with all nodes is:
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0.0
node.physicsBody?.linearDamping = 0.0
node.physicsBody?.angularDamping = 0.0
My question is: How do I make sure the bouncing ball does move at a constant speed? After a couple of bounces, the node is either speeding up, or slowing down.
In order to achieve this, I normalized the vector whenever the physics engine detects a collision. So in func didBegin(_ contact: SKPhysicsContact), the following happens:
let length = hypotf(Float(node.velocity.dx), Float(node.velocity.dy))
let multiplier = (290.0 / length)
firstBody.velocity.dx *= CGFloat(multiplier)
firstBody.velocity.dy *= CGFloat(multiplier)
290.0 is an arbitrary value that determines the speed of ball.
For reference, check this answer.
I have a Pane and inside it I have a Rectangle shape.
The Pane has a red background. What I want is that, in the part where the rectangle is placed, the background is transparent.
This is the code:
Pane pane=new Pane();
pane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));
Rectangle rect=new Rectangle(50,50,50,50, Color.YELLOW);
pane.getChildren().add(rect);
This is the result
This is what I want
In the second picture the grey color is the background color of the stage.
I tried to play with the blending mode but I didn't succeed.
To accomplish your goal, you could try to use 2 instances of Rectangle instead of a Pane and a Rectangle. By doing so, you could use the static substract method of the Shape class to substract the two areas from one another:
Shape.substract(Rect_1, Rect_2);
As stated by Oracle, the method does exactly what you want to do:
public static Shape subtract(Shape shape1,
Shape shape2)
Returns a new Shape which is created by subtracting the specified second shape from the first shape.
The operation works with geometric areas occupied by the input shapes. For a single Shape such area includes the area occupied by the fill if the shape has a non-null fill and the area occupied by the stroke if the shape has a non-null stroke. So the area is empty for a shape with null stroke and null fill. The area of an input shape considered by the operation is independent on the type and configuration of the paint used for fill or stroke. Before the final operation the areas of the input shapes are transformed to the parent coordinate space of their respective topmost parent nodes.
The resulting shape will include areas that were contained only in the first shape and not in the second shape.
shape1 - shape2 = result
+----------------+ +----------------+ +----------------+
|################| |################| | |
|############## | | ##############| |## |
|############ | | ############| |#### |
|########## | | ##########| |###### |
|######## | | ########| |######## |
|###### | | ######| |###### |
|#### | | ####| |#### |
|## | | ##| |## |
+----------------+ +----------------+ +----------------+
Parameters:
shape1 - the first shape
shape2 - the second shape
Returns:
the created Shape
Source
I'm making a tile game in Action Script 3 / Starling.
Suppose I have a scenario like this:
+----+----+----+----+----+
| | | | | | 0th row - highest
+----+----+----+----+----+
| | | | | |
+----+----+----+----+----+
| | | | | |
+----+----+----+----+----+
| | | | | | 3rd row - lowest
+----+----+----+----+----+
In this example, I have a 5x4 tile scenario. In a real gameplay I'll have a 100x100 tile scenario.
This squared map is for an RPG game, where each square could be occupied by an object or a character. Objects can be like trees, which are high and would hide the objects behind.
The key concept here is "hiding objects behind", and the greater is the row number, the "nearest" to the screen it will be, and if the row number is N, rows from 0 to N-1 will be hidden (actually "overlapped") by objects in the layer N (example: if a character is standing on position (x=1,y=2) having a height of 2, and a tree is standing at position (x=1,y=3), the tree would completely overlap the character "above".
I thought about a possible alternative:
If I have the current Starling sprite (which was created by Starling itself passing its class to the Starling constructor), and create one Sprite (sub-sprite) for each row, attaching it to the parent sprite:
private var rows:Vector.<Sprite> = null;
public function createMapRows(nRows:int) {
this.rows = new Vector.<Sprite>();
for(var r:int = 0; r < nRows; r++) {
this.addChild(new Sprite());
}
}
If a character moves left or right, it would remain on the same layer. If a character moves up, I could move the player from the Nth created sprite to the (N-1)th created sprite keeping its (X,Y) coordinates AND performing the 'move up' animation. If a character moves down, I could move the player from the Nth created sprite to the (N-1)th created sprite keeping its (X,Y) coordinates AND performing the 'move down' animation.
I believe this would work but I'm concerned about one aspect: performance.
My Question is: Is this the best way to accomplish this regarding memory performance? Or could be a better solution which does not involve the N+1* problem? (Actually, if the first question has a "NO" answer, I'd like an alternative).
(* No, it's not the N+1 problem regarding database records :p, but amount of sprites).
One thing is to make you'r own sprite instance for every row or just for the objects of that row.
I would do it like this:
The base Sprite object contains all the ground tiles cause there are always under everything else.
And on top of that I would make a Sprite filled with all the objects for that row.
Clouds
Row 0 objects
Row 1 objects
Row 2 objects <-- player is here at the moment
Row 3 objects
BaseLayer - ground tiles
And, like you mentioned before, if the player goes north or south just addChild the player Sprite so the right row Sprite.