Unity gun flipping - visual-studio

I'm making a top down shooter game and when I point my gun backwards, it is upside down. Here's my player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float moveSpeed = 5f;
public Rigidbody2D rb;
public Camera cam;
Vector2 movement;
Vector2 mousePos;
public GameObject crossHair;
public SpriteRenderer sr;
void Start() {
Cursor.visible = false;
}
// Update is called once per frame
void Update()
{
sr = GetComponent<SpriteRenderer>();
rb = GetComponent<Rigidbody2D>();
movement.x = Input.GetAxisRaw("Horizontal");
movement.y = Input.GetAxisRaw("Vertical");
mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
crossHair.transform.position = new Vector2(mousePos.x, mousePos.y);
}
void FixedUpdate()
{
rb.velocity = new Vector2(movement.x, movement.y) * moveSpeed;
Vector3 lookDir = mousePos - rb.position;
lookDir.Normalize();
float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg;
rb.rotation = angle;
if (transform.position.y >= 6.4f) {
transform.position = new Vector2(transform.position.x, 6.4f);
}
if (transform.position.y <= -6.4f) {
transform.position = new Vector2(transform.position.x, -6.4f);
}
if (transform.position.x >= 11.9) {
transform.position = new Vector2(11.9f, transform.position.y);
}
if (transform.position.x <= -11.9) {
transform.position = new Vector2(-11.9f, transform.position.y);
}
float z = transform.rotation.eulerAngles.z;
if (z > 0 && z < 180 && !sr.flipX)
sr.flipX = true;
else if (z < 360 && z > 180 && sr.flipX)
sr.flipX = false;
}
void Flip(){
Vector3 Scaler = transform.localScale;
Scaler.y *= -1;
transform.localScale = Scaler;
}
}
I've searched a lot but I couldn't make it work. I tried with spriteRenderer but it doesn't work either. I really need help.(Btw my player rotates with the player)

Sounds to me like your system is just rotating the object without a second though for what direction it's facing. I imagine if you put an arrow or triangle pointing in the direction you like instead of a gun, it would look just fine.
So what I would do is the following:
if (the rotation is backwards)
render the sprite flipped vertically
else
render the sprite normally

Related

Implementing circle progress bar in libGDX

I want to create a circle progress bar, that will look like this using libgdx library. For now on I've created simple horizontal progressbar using different Images as layers overlapping themselfs and one of them resizing to simulate progress. I found this example, how to draw circle progress bar, but I don't understand how to use this implementation and how can I handle it. The textureRegion in constructor is the progress indicator? and I could not find the method responsible to set the actual progress.
EDIT: I've made my solution basic on this implementation. I put 3 layers which overlapping themselfs and whenever the middle layer swipe's the progress is showed. But Ive have a problem: whenever I try to resize my ProgressCircle instance it move down below the background layer.
public class ProgressCircle extends Image {
public enum IntersectAt {
NONE, TOP, BOTTOM, LEFT, RIGHT;
}
TextureRegion texture;
PolygonSpriteBatch polyBatch;
Vector2 center;
Vector2 centerTop;
Vector2 leftTop;
Vector2 leftBottom;
Vector2 rightBottom;
Vector2 rightTop;
Vector2 progressPoint;
float[] fv;
IntersectAt intersectAt;
private float PROGRESS_WIDTH=0f;
private float PROGRESS_HEIGHT=0f;
private float posX,posY;
public ProgressCircle(TextureRegion region, PolygonSpriteBatch polyBatch,float width,float height,float posX,float posY) {
super(region);
PROGRESS_WIDTH=width;
PROGRESS_HEIGHT=height;
this.posX=posX;
this.posY=posY;
this.texture = region;
this.polyBatch = polyBatch;
center = new Vector2(this.getWidth() / 2, this.getHeight() / 2);
centerTop = new Vector2(this.getWidth() / 2, this.getHeight());
leftTop = new Vector2(0, this.getHeight());
leftBottom = new Vector2(0, 0);
rightBottom = new Vector2(this.getWidth(), 0);
rightTop = new Vector2(this.getWidth(), this.getHeight());
progressPoint = new Vector2(this.getWidth() / 2, this.getHeight() / 2);
setPercentage(0);
}
private Vector2 IntersectPoint(Vector2 line) {
Vector2 v = new Vector2();
boolean isIntersect;
//check top
isIntersect = Intersector.intersectSegments(leftTop, rightTop, center, line, v);
//check bottom
if (isIntersect) {
intersectAt = IntersectAt.TOP;
return v;
} else
isIntersect = Intersector.intersectSegments(leftBottom, rightBottom, center, line, v);
//check left
if (isIntersect) {
intersectAt = IntersectAt.BOTTOM;
return v;
} else isIntersect = Intersector.intersectSegments(leftTop, leftBottom, center, line, v);
//check bottom
if (isIntersect) {
intersectAt = IntersectAt.LEFT;
return v;
} else isIntersect = Intersector.intersectSegments(rightTop, rightBottom, center, line, v);
if (isIntersect) {
intersectAt = IntersectAt.RIGHT;
return v;
} else {
intersectAt = IntersectAt.NONE;
return null;
}
}
public void setPercentage(float percent) {
//100 % = 360 degree
float angle = convertToRadians(90);
angle -= convertToRadians(percent * 360 / 100);
float len = this.getWidth() > this.getHeight() ? this.getWidth() : this.getHeight();
float dy = (float) (Math.sin(angle) * len);
float dx = (float) (Math.cos(angle) * len);
Vector2 line = new Vector2(center.x + dx, center.y + dy);
Vector2 v = IntersectPoint(line);
if (intersectAt == IntersectAt.TOP) {
if (v.x >= this.getWidth() / 2) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
rightBottom.x,
rightBottom.y,
rightTop.x,
rightTop.y,
v.x,
v.y
};
} else {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
v.x,
v.y
};
}
} else if (intersectAt == IntersectAt.BOTTOM) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
v.x,
v.y
};
} else if (intersectAt == IntersectAt.LEFT) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
v.x,
v.y
};
} else if (intersectAt == IntersectAt.RIGHT) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
rightBottom.x,
rightBottom.y,
v.x,
v.y
};
} else // if (intersectAt == IntersectAt.NONE)
{
fv = null;
}
}
#Override
public void draw(Batch batch, float parentAlpha) {
if (fv == null) return;
batch.end();
drawMe();
batch.begin();
}
public void drawMe() {
EarClippingTriangulator e = new EarClippingTriangulator();
ShortArray sv = e.computeTriangles(fv);
PolygonRegion polyReg = new PolygonRegion(texture, fv, sv.toArray());
PolygonSprite poly = new PolygonSprite(polyReg);
poly.setSize(PROGRESS_WIDTH,PROGRESS_HEIGHT);
poly.setPosition(posX,posY);
poly.setOrigin(poly.getOriginX(), poly.getOriginY());
poly.setRotation(this.getRotation());
poly.setColor(Color.GREEN);
polyBatch.begin();
poly.draw(polyBatch);
polyBatch.end();
}
float convertToDegrees(float angleInRadians) {
float angleInDegrees = angleInRadians * 57.2957795f;
return angleInDegrees;
}
float convertToRadians(float angleInDegrees) {
float angleInRadians = angleInDegrees * 0.0174532925f;
return angleInRadians;
}
}
and my init function:
ProgressCircle sprite;
private void initCircleProgress() {
Group group = new Group();
Image downBackground = new Image(atlas.findRegion("progressBackground"));
downBackground.setColor(Color.BLUE);
downBackground.setSize(USER_SCREEN_SIZE_WIDTH/5,USER_SCREEN_SIZE_WIDTH/5);
downBackground.setPosition(10,USER_SCREEN_SIZE_HEIGHT-(2*downBackground.getHeight()+20));
sprite = new ProgressCircle(atlas.findRegion("progressBackground"), pbatch,
downBackground.getWidth(),downBackground.getHeight(),downBackground.getX(),downBackground.getY());
sprite.setSize(downBackground.getWidth(),downBackground.getHeight());
sprite.setSize(downBackground.getX(),downBackground.getY());
Image label = new Image(atlas.findRegion("progressBackground"));
label.setSize(downBackground.getWidth()*0.8f,downBackground.getHeight()*0.8f);
label.setPosition(downBackground.getX()+(downBackground.getWidth()-label.getWidth())/2,
downBackground.getY()+(downBackground.getHeight()-label.getHeight())/2);
label.setColor(Color.WHITE);
group.addActor(downBackground);
group.addActor(sprite);
group.addActor(label);
group.setPosition(10,GAME_SIZE_HEIGHT-(group.getHeight()+20));
stage.addActor(group);
}
You need a circular gradient to mask out the desired parts.
Incrementing the masking values over time will result in a circular motion.
As you can see in the link to masking there are several methods to achieve this. A stencil buffer would be a good choice but can result in jaggy edges. Writing your own shader would be perfect but kinda hard if you never wrote a shader. You can also try to parse both pix maps and decide what pixels to draw while looping through both pixmaps. If the masking image and your source have equal texture space you can do this in one loop so it would not be that much slower then a shader and whatever works for you is a good solution, you can always optimize later.

Java FX minigolf game - how to slow down the moving ball

I am beginner and trying to create a simple minigolf game with Java FX.
I built small golfcourse using lines. I get user input about hit strength from the slider and can make a hit using keyboard into 8 different directions.
If I hit for example button H, ball starts to move to right. If slider value was minimum, it moves slowly and if its maximum, it moves very fast.
But I cannot make the movement slow down until it stops.
Any ideas how to do that? My code so far is below.
Thank You in advance!
package sample;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Main extends Application {
double directionx = 0;
double directiony = 0;
Line line1,line2,line3,line4,line5,line6,line7,line8,line9;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Minigolf");
Pane pane = new Pane();
Scene scene = new Scene(pane, 900, 600);
pane.setStyle("-fx-background-color: green");
primaryStage.setScene(scene);
Slider slider = new Slider();
slider.setMin(1);
slider.setMax(10);
slider.setValue(1);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setLayoutX(10);
slider.setLayoutY(10);
Circle ball = new Circle(10);
Circle hole = new Circle(15);
hole.setCenterX(75);
hole.setCenterY(430);
hole.setFill(Color.YELLOW);
ball.setCenterX(100);
ball.setCenterY(300);
// creating golfcourse with lines
line1 = new Line(50,200,500,200);
line2 = new Line(50,350,650,350);
line3 = new Line(50,200,50,350);
line4 = new Line(500,200,500,50);
line5 = new Line(650,350,650,200);
line6 = new Line(500,50,800,50);
line7 = new Line(800,50,800,500);
line8 = new Line(800,500,50,500);
line9 = new Line(50,500,50,350);
pane.getChildren().addAll(slider,ball,hole,line1,line2,line3,line4,line5,line6,line7,line8,line9);
new AnimationTimer() {
#Override
public void handle(long now) {
double ballY = ball.getCenterY();
double newballY = ballY + directiony;
// if ball touches vertical walls
if (ball.getBoundsInParent().intersects(line5.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line3.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line4.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line7.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line9.getBoundsInParent())) {
// changes direction
directionx = directionx * -1;
}
ball.setCenterX(ball.getCenterX() + directionx);
ball.setCenterY(newballY);
// if ball touches horizontal walls
if (ball.getBoundsInParent().intersects(line1.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line2.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line6.getBoundsInParent())
|| ball.getBoundsInParent().intersects(line8.getBoundsInParent())) {
directiony = directiony * -1;
}
// if ball touches hole, then game is over
if (ball.getBoundsInParent().intersects(hole.getBoundsInParent())) {
System.out.println("Game over");
pane.getChildren().removeAll(ball);
}
}
}.start();
// I get the slider value and making array where are 10 different strengts
scene.setOnKeyPressed(event -> {
double b = Math.round(slider.getValue());
b--;
int c = (int) b;
double [] strengts = new double[10];
double k = 0.1;
for (int i = 0; i < strengts.length; i++) {
strengts[i] = k;
k = k + 0.5;
}
// 8 different directions to move the ball
if (event.getCode().equals(KeyCode.H)) {
directionx = directionx + strengts[c];
}
else if(event.getCode().equals(KeyCode.F)) {
directionx = directionx - strengts[c];
}
else if(event.getCode().equals(KeyCode.V)) {
directiony=directiony + strengts[c];
}
else if(event.getCode().equals(KeyCode.T)) {
directiony= directiony - strengts[c];
}
else if(event.getCode().equals(KeyCode.B))
{
directionx = directionx + strengts[c];
directiony = directiony + strengts[c];
}
else if(event.getCode().equals(KeyCode.Y))
{
directionx = directionx + strengts[c];
directiony = directiony - strengts[c];
}
else if(event.getCode().equals(KeyCode.R))
{
directionx = directionx - strengts[c];
directiony = directiony - strengts[c];
}
else if(event.getCode().equals(KeyCode.C))
{
directionx = directionx - strengts[c];
directiony = directiony + strengts[c];
}
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Just use physics for friction. Assume for every frame the speed (v) remains constant.
Energy of a ball (mass m):
E = 0.5 * m * v²
Work for friction based on distance (s) traveled and a friction constant μ
ΔE = s * m * μ = Δt * v * m * μ
The new energy is E - ΔE which you can use with the first formula to calculate the new speed v':
0.5 * m * v'² = 0.5 * m * v² - s * m * μ
v'² = v² - s * μ * 2
v' = sqrt(v² - s * μ * 2)
The above assumes the ground is flat.
This allows you to decrease the speed like this:
#Override
public void start(Stage primaryStage) {
Point2D direction = new Point2D(1, 1).normalize();
Circle c = new Circle(10, 10, 10);
Timeline tl = new Timeline();
tl.getKeyFrames().setAll(new KeyFrame(Duration.seconds(1 / 60d), new EventHandler() {
double speed = 3;
double friction = 0.02;
#Override
public void handle(Event event) {
double distance = speed;
Point2D movement = direction.multiply(distance);
c.setCenterX(c.getCenterX() + movement.getX());
c.setCenterY(c.getCenterY() + movement.getY());
double energyBefore = speed * speed;
double energyLost = distance * friction;
if (energyLost > energyBefore) {
tl.stop();
speed = 0;
System.out.println("stopped");
} else {
speed = Math.sqrt(energyBefore - energyLost);
}
}
}));
tl.setCycleCount(Animation.INDEFINITE);
Pane root = new Pane();
root.getChildren().add(c);
Scene scene = new Scene(root, 400, 400);
scene.setOnMouseClicked(evt -> tl.play());
primaryStage.setScene(scene);
primaryStage.show();
}
Thank You for the reply, but as a beginner I found now by myself simpler solution.
I just add 4 if-statements as follows:
if (directionx>0) {
directionx = directionx - 0.01;
}
if(directionx<0){
directionx = directionx + 0.01;
}
if(directiony>0) {
directiony = directiony - 0.01;
}
if(directiony<0) {
directiony = directiony + 0.01;
}
Perhaps the in real physically its somehow wrong, but it looks quite real for me.

I cannot get my animations to play I am Using slick2d and a SpriteSheet 64 by 64

I am using slick2d I have come across this problem where I cannot get this sprite sheet to animate at all I have tried most of the methods using the update function.
package javagame;
import org.newdawn.slick.*;
import org.newdawn.slick.state.*;
public class Play extends BasicGameState{
Image worldMap;
boolean quit = false;
int[] duration = {200,200};
float monsterPositionX = 0;
float monsterPositionY = 0;
float shiftX = monsterPositionX +320;
float shiftY = monsterPositionY +160;
public Image monsterImage = null;
public SpriteSheet monsterSheet;
public Animation monster;
public Animation monsterAnimationDown;
public Animation monsterAnimationUp;
public Animation monsterAnimationLeft;
public Animation monsterAnimationRight;
public Play(int state){
}
public void init(GameContainer gc, StateBasedGame sbg)throws SlickException{
monsterImage = new Image("/res/Monster.png");
monsterSheet = new SpriteSheet(monsterImage, 64 , 64);
monsterAnimationUp = new Animation(monsterSheet, 0,8,8,8,true,200,false);
monsterAnimationLeft = new Animation(monsterSheet, 0,9,8,9,true,200,false);
monsterAnimationDown = new Animation(monsterSheet, 0,10,8,10,true,200,false);
monsterAnimationRight = new Animation(monsterSheet, 0,11,8,11,true,200,false);
monster = monsterAnimationDown;
}
//renders images to screen0
public void render(GameContainer gc, StateBasedGame sbg, Graphics g)throws SlickException{
monster.draw(shiftX,shiftY);
if(quit == true){
g.drawString("Resume (R)", 250, 100);
g.drawString("Main Menu (M)", 250, 150);
g.drawString("Quit Game (Q)", 250, 200);
if(quit == false){
g.clear();
}
}
}
// changes translations of game objects
public void update(GameContainer gc, StateBasedGame sbg, int delta)throws SlickException{
Input input = gc.getInput();
if(input.isKeyDown(Input.KEY_W)){
monster = monsterAnimationUp;
monster.setPingPong(true);
monsterPositionY += delta *.1f;
}else if(input.isKeyDown(Input.KEY_S)){
monster = monsterAnimationDown;
monsterPositionY -= delta *.1f;
} else if(input.isKeyDown(Input.KEY_D)){
monster = monsterAnimationRight;
monsterPositionX += delta *.1f;
}else if(input.isKeyDown(Input.KEY_A)){
monster = monsterAnimationLeft;
monsterPositionX -= delta *.1f;
}
if(input.isKeyDown(Input.KEY_W)&& input.isKeyDown(Input.KEY_D)){
monster = monsterAnimationUp;
monster.setPingPong(true);
monsterPositionY += delta *.1f;
monsterPositionX += delta *.1f;
}if(input.isKeyDown(Input.KEY_W)&& input.isKeyDown(Input.KEY_A)){
monster = monsterAnimationUp;
monster.setPingPong(true);
monsterPositionY += delta *.1f;
monsterPositionX -= delta *.1f;
}if(input.isKeyDown(Input.KEY_S)&& input.isKeyDown(Input.KEY_D)){
monster = monsterAnimationDown;
monster.setPingPong(true);
monsterPositionY -= delta *.1f;
monsterPositionX += delta *.1f;
}if(input.isKeyDown(Input.KEY_S)&& input.isKeyDown(Input.KEY_A)){
monster = monsterAnimationDown;
monster.setPingPong(true);
monsterPositionY -= delta *.1f;
monsterPositionX -= delta *.1f;
}
}
}
Could you provide a way to make the animation work please?
I'm not used to this method but maybe you have to start the animation.
Then, you should update your animation object by giving the delta in your update method.
In your Play.update:
monster.update(delta);
Hope this helps

How to render Sprites normally on rotated isometric tiles? LIBGDX

i was using this tutorial to make an isometric Map from normal Sprites:
http://www.badlogicgames.com/wordpress/?p=2032
I want to render a house on top of my floor tiles at position 0,0, but it looks like this:
http://gyazo.com/c7d5cd74829150c684cfa7b40c9fdfba
Here is my create() and render():
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture(Gdx.files.internal("grass2d.jpg"));
//House
house = new Sprite(new Texture(Gdx.files.internal("mb_0004.png")));
house.setSize(2,2);
house.setPosition(0,0);
// Camera
cam = new OrthographicCamera(10,10*(Gdx.graphics.getHeight()/(float)Gdx.graphics.getWidth()));
cam.position.set(5,5,10);
cam.direction.set(-1,-1,-1);
cam.near = 1;
cam.far = 100;
//Matrices
//Matrix to rotate floor tiles
matrix.setToRotation(new Vector3(1,0,0), 90);
// Subject to change?
matrix2.setToRotation(new Vector3(0,1,0),45);
// populate Tiles
for(int z = 0; z < 100; z++) {
for(int x = 0; x < 100; x++) {
sprites[x][z] = new Sprite(img);
sprites[x][z].setPosition(x,z);
sprites[x][z].setSize(1, 1);
}
}
Gdx.input.setInputProcessor(this);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.update();
batch.setProjectionMatrix(cam.combined);
batch.setTransformMatrix(matrix);
batch.begin();
for(int z = 0; z < 100; z++) {
for(int x = 0; x < 100; x++) {
sprites[x][z].draw(batch);
}
}
batch.end();
batch.begin();
//batch.setTransformMatrix(matrix2);
house.draw(batch);
batch.end();
I did try to create another Matrix to transform it back, but then my coordinates are messed up. Hope you guys can help.
i was able to do it using TiledMap and a self written algorithm to detect wether or not a tile has been clicked and which one. I am able to place my house by mouse now and really like the result.
If anybody is interested, i will take some time to write a small tutorial.
Here is what it looks like at the moment:
Isometric Tiles

How to scroll to a specific element in ScrollRect with Unity UI?

I built a registration form for a mobile game using Unity 5.1.
To do that, I use Unity UI components: ScrollRect + Autolayout (Vertical layout) + Text (labels) + Input Field.
This part works fine.
But, when keyboard is opened, the selected field is under keyboard. Is there a way to programmatically scroll the form to bring the selected field into view?
I have tried using ScrollRect.verticalNormalizedPosition and it works fine to scroll some, however I am not able to make selected field appear where I want.
Thanks for your help !
I am going to give you a code snippet of mine because I feel like being helpful. Hope this helps!
protected ScrollRect scrollRect;
protected RectTransform contentPanel;
public void SnapTo(RectTransform target)
{
Canvas.ForceUpdateCanvases();
contentPanel.anchoredPosition =
(Vector2)scrollRect.transform.InverseTransformPoint(contentPanel.position)
- (Vector2)scrollRect.transform.InverseTransformPoint(target.position);
}
None of the suggestions worked for me, the following code did
Here is the extension
using UnityEngine;
using UnityEngine.UI;
namespace BlinkTalk
{
public static class ScrollRectExtensions
{
public static Vector2 GetSnapToPositionToBringChildIntoView(this ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
return result;
}
}
}
And here is how I used it to scroll a direct child of the content into view
private void Update()
{
MyScrollRect.content.localPosition = MyScrollRect.GetSnapToPositionToBringChildIntoView(someChild);
}
Although #maksymiuk's answer is the most correct one, as it properly takes into account anchors, pivot and all the rest thanks to InverseTransformPoint() function, it still didn't work out-of-box for me - for vertical scroller, it was changing its X position too. So I just made change to check if vertical or horizontal scroll is enabled, and not change their axis if they aren't.
public static void SnapTo( this ScrollRect scroller, RectTransform child )
{
Canvas.ForceUpdateCanvases();
var contentPos = (Vector2)scroller.transform.InverseTransformPoint( scroller.content.position );
var childPos = (Vector2)scroller.transform.InverseTransformPoint( child.position );
var endPos = contentPos - childPos;
// If no horizontal scroll, then don't change contentPos.x
if( !scroller.horizontal ) endPos.x = contentPos.x;
// If no vertical scroll, then don't change contentPos.y
if( !scroller.vertical ) endPos.y = contentPos.y;
scroller.content.anchoredPosition = endPos;
}
here's the way I clamped selected object into ScrollRect
private ScrollRect scrollRect;
private RectTransform contentPanel;
public void ScrollReposition(RectTransform obj)
{
var objPosition = (Vector2)scrollRect.transform.InverseTransformPoint(obj.position);
var scrollHeight = scrollRect.GetComponent<RectTransform>().rect.height;
var objHeight = obj.rect.height;
if (objPosition.y > scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y - objHeight - Padding.top);
}
if (objPosition.y < -scrollHeight / 2)
{
contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y + objHeight + Padding.bottom);
}
}
The preconditions for my version of this problem:
The element I want to scroll to should be fully visible (with minimal clearance)
The element is a direct child of the scrollRect's content
Keep scoll position if element is already fully visible
I only care about the vertical dimension
This is what worked best for me (thanks for the other inspirations):
// ScrollRect scrollRect;
// RectTransform element;
// Fully show `element` inside `scrollRect` with at least 25px clearance
scrollArea.EnsureVisibility(element, 25);
Using this extension method:
public static void EnsureVisibility(this ScrollRect scrollRect, RectTransform child, float padding=0)
{
Debug.Assert(child.parent == scrollRect.content,
"EnsureVisibility assumes that 'child' is directly nested in the content of 'scrollRect'");
float viewportHeight = scrollRect.viewport.rect.height;
Vector2 scrollPosition = scrollRect.content.anchoredPosition;
float elementTop = child.anchoredPosition.y;
float elementBottom = elementTop - child.rect.height;
float visibleContentTop = -scrollPosition.y - padding;
float visibleContentBottom = -scrollPosition.y - viewportHeight + padding;
float scrollDelta =
elementTop > visibleContentTop ? visibleContentTop - elementTop :
elementBottom < visibleContentBottom ? visibleContentBottom - elementBottom :
0f;
scrollPosition.y += scrollDelta;
scrollRect.content.anchoredPosition = scrollPosition;
}
A variant of Peter Morris answer for ScrollRects which have movement type "elastic". It bothered me that the scroll rect kept animating for edge cases (first or last few elements). Hope it's useful:
/// <summary>
/// Thanks to https://stackoverflow.com/a/50191835
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static IEnumerator BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
Canvas.ForceUpdateCanvases();
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 result = new Vector2(
0 - (viewportLocalPosition.x + childLocalPosition.x),
0 - (viewportLocalPosition.y + childLocalPosition.y)
);
instance.content.localPosition = result;
yield return new WaitForUpdate();
instance.horizontalNormalizedPosition = Mathf.Clamp(instance.horizontalNormalizedPosition, 0f, 1f);
instance.verticalNormalizedPosition = Mathf.Clamp(instance.verticalNormalizedPosition, 0f, 1f);
}
It introduces a one frame delay though.
UPDATE: Here is a version which does not introduce a frame delay and it also takes scaling of the content into account:
/// <summary>
/// Based on https://stackoverflow.com/a/50191835
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static void BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
instance.content.ForceUpdateRectTransforms();
instance.viewport.ForceUpdateRectTransforms();
// now takes scaling into account
Vector2 viewportLocalPosition = instance.viewport.localPosition;
Vector2 childLocalPosition = child.localPosition;
Vector2 newContentPosition = new Vector2(
0 - ((viewportLocalPosition.x * instance.viewport.localScale.x) + (childLocalPosition.x * instance.content.localScale.x)),
0 - ((viewportLocalPosition.y * instance.viewport.localScale.y) + (childLocalPosition.y * instance.content.localScale.y))
);
// clamp positions
instance.content.localPosition = newContentPosition;
Rect contentRectInViewport = TransformRectFromTo(instance.content.transform, instance.viewport);
float deltaXMin = contentRectInViewport.xMin - instance.viewport.rect.xMin;
if(deltaXMin > 0) // clamp to <= 0
{
newContentPosition.x -= deltaXMin;
}
float deltaXMax = contentRectInViewport.xMax - instance.viewport.rect.xMax;
if (deltaXMax < 0) // clamp to >= 0
{
newContentPosition.x -= deltaXMax;
}
float deltaYMin = contentRectInViewport.yMin - instance.viewport.rect.yMin;
if (deltaYMin > 0) // clamp to <= 0
{
newContentPosition.y -= deltaYMin;
}
float deltaYMax = contentRectInViewport.yMax - instance.viewport.rect.yMax;
if (deltaYMax < 0) // clamp to >= 0
{
newContentPosition.y -= deltaYMax;
}
// apply final position
instance.content.localPosition = newContentPosition;
instance.content.ForceUpdateRectTransforms();
}
/// <summary>
/// Converts a Rect from one RectTransfrom to another RectTransfrom.
/// Hint: use the root Canvas Transform as "to" to get the reference pixel positions.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Rect TransformRectFromTo(Transform from, Transform to)
{
RectTransform fromRectTrans = from.GetComponent<RectTransform>();
RectTransform toRectTrans = to.GetComponent<RectTransform>();
if (fromRectTrans != null && toRectTrans != null)
{
Vector3[] fromWorldCorners = new Vector3[4];
Vector3[] toLocalCorners = new Vector3[4];
Matrix4x4 toLocal = to.worldToLocalMatrix;
fromRectTrans.GetWorldCorners(fromWorldCorners);
for (int i = 0; i < 4; i++)
{
toLocalCorners[i] = toLocal.MultiplyPoint3x4(fromWorldCorners[i]);
}
return new Rect(toLocalCorners[0].x, toLocalCorners[0].y, toLocalCorners[2].x - toLocalCorners[1].x, toLocalCorners[1].y - toLocalCorners[0].y);
}
return default(Rect);
}
In case anyone looking for a smooth scroll (using lerp).
[SerializeField]
private ScrollRect _scrollRectComponent; //your scroll rect component
[SerializeField]
RectTransform _container; //content transform of the scrollrect
private IEnumerator LerpToChild(RectTransform target)
{
Vector2 _lerpTo = (Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - (Vector2)_scrollRectComponent.transform.InverseTransformPoint(target.position);
bool _lerp = true;
Canvas.ForceUpdateCanvases();
while(_lerp)
{
float decelerate = Mathf.Min(10f * Time.deltaTime, 1f);
_container.anchoredPosition = Vector2.Lerp(_scrollRectComponent.transform.InverseTransformPoint(_container.position), _lerpTo, decelerate);
if (Vector2.SqrMagnitude((Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - _lerpTo) < 0.25f)
{
_container.anchoredPosition = _lerpTo;
_lerp = false;
}
yield return null;
}
}
simple and works perfectly
var pos = 1 - ((content.rect.height / 2 - target.localPosition.y) / content.rect.height);
scrollRect.normalizedPosition = new Vector2(0, pos);
Yes,this is possible using coding to scroll vertically, please try this code :
//Set Scrollbar Value - For Displaying last message of content
Canvas.ForceUpdateCanvases ();
verticleScrollbar.value = 0f;
Canvas.ForceUpdateCanvases ();
This code working fine for me ,when i developed chat functionality.
width signifiy the width of childern in scroll rect (assuming that all childerns width is same), spacing signifies the space between childerns, index signifies the target element you want to reach
public float getSpecificItem (float pWidth, float pSpacing,int pIndex) {
return (pIndex * pWidth) - pWidth + ((pIndex - 1) * pSpacing);
}
Vertical adjustment:
[SerializeField]
private ScrollRect _scrollRect;
private void ScrollToCurrentElement()
{
var siblingIndex = _currentListItem.transform.GetSiblingIndex();
float pos = 1f - (float)siblingIndex / _scrollRect.content.transform.childCount;
if (pos < 0.4)
{
float correction = 1f / _scrollRect.content.transform.childCount;
pos -= correction;
}
_scrollRect.verticalNormalizedPosition = pos;
}
Here's what I created to solve this problem. Place this behavior on each button that can be selected via controller UI navigation.
It supports fully nested children objects.
It only supports vertical scrollrects, but can be easily adapted to do horizontal.
using UnityEngine;
using System;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Legend
{
public class ScrollToOnSelect: MonoBehaviour, ISelectHandler
{
ScrollRect scrollRect;
RectTransform target;
void Start()
{
scrollRect = GetComponentInParent<ScrollRect>();
target = (RectTransform)this.transform;
}
Vector3 LocalPositionWithinAncestor(Transform ancestor, Transform target)
{
var result = Vector3.zero;
while (ancestor != target && target != null)
{
result += target.localPosition;
target = target.parent;
}
return result;
}
public void EnsureScrollVisible()
{
Canvas.ForceUpdateCanvases();
var targetPosition = LocalPositionWithinAncestor(scrollRect.content, target);
var top = (-targetPosition.y) - target.rect.height / 2;
var bottom = (-targetPosition.y) + target.rect.height / 2;
var topMargin = 100; // this is here because there are headers over the buttons sometimes
var result = scrollRect.content.anchoredPosition;
if (result.y > top - topMargin)
result.y = top - topMargin;
if (result.y + scrollRect.viewport.rect.height < bottom)
result.y = bottom - scrollRect.viewport.rect.height;
//Debug.Log($"{targetPosition} {target.rect.size} {top} {bottom} {scrollRect.content.anchoredPosition}->{result}");
scrollRect.content.anchoredPosition = result;
}
public void OnSelect(BaseEventData eventData)
{
if (scrollRect != null)
EnsureScrollVisible();
}
}
}

Resources