Ok, I am having a problem with a Unity scroll panel in C#. I downloaded this character picker that consists of a script attached to a scroll panel from here:
https://www.youtube.com/watch?v=YBsGhYuTavA
It works very well but problem is I cant make it scroll vertically instead of horizontal. I have checked the "vertical" boolean instead of horizontal on the actual scroll panel then in the script I have changed the places that based off x values to y values.
I commented where, here is the script:
float[] distance;
bool dragging = false;
int minButtonNum;
int currentSelectedPly = -1;
public float objectScale = 1.7f;
public int bttnDistance = 300;
void OnEnable() {
//txtGeneralCash.text = "" + PlayerPrefs.GetInt ("money", 0);
}
void Start(){
distance = new float[prefab.Length];
//instatiate the prefab
for(int i=0; i<prefab.Length;i++){
prefab[i] = Instantiate(prefab[i],center.transform.position,camInUse.transform.rotation) as GameObject;
prefab [i].transform.SetParent(panel.transform);
Vector3 pos = prefab[i].GetComponent<RectTransform>().anchoredPosition;
pos.x += (i * bttnDistance); //**CHANGED TO POS.Y
prefab [i].GetComponent<RectTransform> ().anchoredPosition = pos;
}
}
void Update(){
//calculate the relative distance
for(int i=0;i<prefab.Length;i++){
distance [i] = Mathf.Abs (center.transform.position.x - prefab [i].transform.position.x); //CHANGED THESE TO .Y
}
float minDistance = Mathf.Min (distance);
// Aplly the scale to object
for(int a=0;a<prefab.Length;a++){
if (minDistance == distance [a]) {
minButtonNum = a;
//this is when each char is selected !!!!!!!!!!!!!!!
if(minButtonNum != currentSelectedPly){
//lookAtPrice (minButtonNum);
scaleButtonCenter (minButtonNum);
currentSelectedPly = minButtonNum;
txtName.text = prefab [minButtonNum].GetComponent<CharacterProperty> ().nameObj;
bgMat.color = prefab [minButtonNum].GetComponent<CharacterProperty> ().color;
}
}
}
// if the users aren't dragging the lerp function is called on the prefab
if(!dragging){
LerpToBttn (currentSelectedPly* (-bttnDistance));
}
}
/*
* Lerp the nearest prefab to center
*/
void LerpToBttn(int position){
float newX = Mathf.Lerp (panel.anchoredPosition.x,position,Time.deltaTime*7f); //CHANGED TO .Y
Vector2 newPosition = new Vector2 (newX,panel.anchoredPosition.y);
panel.anchoredPosition = newPosition;
}
/*
* Set the scale of the prefab on center to 2, other to 1
*/
public void scaleButtonCenter (int minButtonNum){
for (int a = 0; a < prefab.Length; a++) {
if (a == minButtonNum) {
StartCoroutine (ScaleTransform(prefab [a].transform,prefab [a].transform.localScale,new Vector3 (objectScale,objectScale,objectScale)));
} else {
StartCoroutine (ScaleTransform(prefab [a].transform,prefab [a].transform.localScale,new Vector3 (1f, 1f, 1f)));
}
}
}
/*
* If the prefab is not free, show the price button
/*
* Courutine for change the scale
*/
IEnumerator ScaleTransform(Transform transformTrg,Vector3 initScale,Vector3 endScale){
float completeTime = 0.2f;//How much time will it take to scale
float currentTime = 0.0f;
bool done = false;
while (!done){
float percent = currentTime / completeTime;
if (percent >= 1.0f){
percent = 1;
done = true;
}
transformTrg.localScale = Vector3.Lerp(initScale, endScale, percent);
currentTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
}
/*
* Called by the canvas, set dragging to true for preventing lerp when users are dragging
*/
public void StartDrag(){
dragging = true;
}
/*
* Called by the canvas, set dragging to true for preventing lerp when users are dragging
*/
public void EndDrag(){
dragging = false;
}
/*
* Called when character is selected, it change the player model
*/
public void CharacterSelected(){
bool oneEnable = false;
string nameSelected = prefab [currentSelectedPly].GetComponent<CharacterProperty> ().name;
nameSelected = nameSelected.Split('(')[0];
GameObject player = GameObject.Find ("CharactersPlayer");
if(player != null){
foreach (Transform child in player.transform) {
if (child.gameObject.name == nameSelected) {
child.gameObject.SetActive (true);
oneEnable = true;
PlayerPrefs.SetString ("SelectedPlayer", nameSelected);
} else {
child.gameObject.SetActive (false);
}
}
// if no one was selected
if (oneEnable == false) {
player.transform.GetChild (0).gameObject.SetActive (true);
}
}
}
Ive gone through it several times and I don't know why just changing the values from .x to .y does not work. I get a bunch of glitching and the characters do stack up vertically, but they move off the screen when i try to scroll.
How can I fix this?
Related
So decided to give game dev a try, picked up unity,Now I decided to create a simple ping pong game.
My game has Bat.cs class, ball.cs, and GameHandler.cs.
The GameHandler.cs initializes the batRight, batLeft, ball using the Bat.cs, Ball.cs class and prefabs, it also keeps track of if someone scores then starts a serve by stopping the rally:
public class GameHandler : MonoBehaviour
{
public Bat bat;
public Ball ball;
public Score score;
public static Vector2 bottomLeft;
public static Vector2 topRight;
public static bool rallyOn = false;
public static bool isServingLeft = true;
public static bool isServingRight = false;
public static bool isTwoPlayers = true;
static Bat batRight;
static Bat batLeft;
public static Ball ball1;
static Score scoreLeft;
static Score scoreRight;
// Start is called before the first frame update
void Start()
{
//Convert screens pixels coordinate into games coordinate
bottomLeft = Camera.main.ScreenToWorldPoint(new Vector2(0, 0));
topRight = Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height));
ball1 = Instantiate(ball) as Ball;
//Debug.Log("GameHandler.Start");
batRight = Instantiate(bat) as Bat;
batRight.Init(true);
batLeft = Instantiate(bat) as Bat;
batLeft.Init(false);
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
//Instatiate scores
scoreLeft = Instantiate(score, new Vector2(-2, -4), Quaternion.identity) as Score;
scoreRight = Instantiate(score, new Vector2(2, -4), Quaternion.identity) as Score;
}
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
if (GameHandler.rallyOn)
{
StopCoroutine(batRight.serveAi());
}
}
}
}
public static void increaseScoreByOne(bool isRight)
{
rallyOn = false;
if (isRight)
{
scoreRight.increaseScore();
isServingRight = true;
}
else
{
scoreLeft.increaseScore();
isServingLeft = true;
}
}
}
The ball.cs listens to the static GameHandler.rallyOn and starts moving the ball accordingly, it also changes direction of the ball if it hits a vertical wall or the bat:
public class Ball : MonoBehaviour
{
[SerializeField]
float speed;
float radius;
public Vector2 direction;
public Vector3 ballPosition;
// Start is called before the first frame update
void Start()
{
direction = Vector2.one.normalized; // direction is (1, 1) normalized
//Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
radius = transform.localScale.x / 2;
}
// Update is called once per frame
void Update()
{
ballPosition = transform.position;
if (GameHandler.rallyOn)
{
startRally();
}
}
void startRally()
{
transform.Translate(direction * speed * Time.deltaTime);
Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
if ((transform.position.y + radius) > GameHandler.topRight.y && direction.y > 0)
{
direction.y = -direction.y;
}
if ((transform.position.y + radius) < GameHandler.bottomLeft.y && direction.y < 0)
{
direction.y = -direction.y;
}
if ((transform.position.x + radius) > GameHandler.topRight.x && direction.x > 0)
{
// Left player scores
GameHandler.increaseScoreByOne(false);
//For no, just freeza the script
// Time.timeScale = 0;
//enabled = false;
}
if ((transform.position.x - radius) < GameHandler.bottomLeft.x && direction.x < 0)
{
// right player scores
GameHandler.increaseScoreByOne(true);
}
}
void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag == "Bat")
{
if (collision.GetComponent<Bat>().isRight && direction.x > 0)
{
direction.x = -direction.x;
}
if (!collision.GetComponent<Bat>().isRight && direction.x < 0)
{
direction.x = -direction.x;
}
}
}
public float getRadius()
{
return radius;
}
}
The bat.cs initiazes the two paddles, and either listens to user input if its 2 players or listens to player and use AI if it is player vs CPU.:
public class Bat : MonoBehaviour
{
float height;
[SerializeField] // this make this appear on editor without making this field public
float speed;
string input;
public bool isRight;
string PLAYER1_INPUT = "PaddleLeft";
string PLAYER2_INPUT = "PaddleRight";
// Start is called before the first frame update
void Start()
{
height = transform.localScale.y;
}
// Update is called once per frame
void Update()
{
if (GameHandler.isTwoPlayers)
{
if (isRight)
{
movePaddleonUserInput(PLAYER2_INPUT);
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
else
{
if (isRight)
{
movePaddleAi();
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
}
void movePaddleAi()
{
if (isRight && GameHandler.ball1.direction.x > 0 && GameHandler.ball1.ballPosition.x > 0)
{
//transform.Translate(direction * speed * Time.deltaTime);
if ((transform.position.y) > GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y < 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
if ((transform.position.y) < GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y > 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
}
}
void movePaddleonUserInput(string input)
{
// move = (-1 -> 1) * speed * timeDelta(this keep move framerate independent)
float move = Input.GetAxis(input) * speed * Time.deltaTime;
//Debug.Log((transform.position.y + height / 2) + " > "+ GameHandler.topRight.y+ "&&" + move +" > 0");
//Restrict paddle movement to to the screen
// (top edge of paddle > Screen top and we are moving up)
if ((transform.position.y + height / 2) > GameHandler.topRight.y && move > 0)
{
move = 0;
}
// (bottom edge of paddle < Screen top and we are moving down)
if ((transform.position.y - height / 2) < GameHandler.bottomLeft.y && move < 0)
{
move = 0;
}
transform.Translate(move * Vector2.up);
}
public void Init(bool isRightPaddle)
{
isRight = isRightPaddle;
Vector2 pos;
if (isRightPaddle)
{
isRight = isRightPaddle;
pos = new Vector2(GameHandler.topRight.x, 0);
// offset since center is the anchor
pos -= Vector2.right * transform.localScale.x;
input = "PaddleRight";
}
else
{
pos = new Vector2(GameHandler.bottomLeft.x, 0);
// offset since center is the anchor
pos += Vector2.right * transform.localScale.x;
input = "PaddleLeft";
}
transform.name = input;
transform.position = pos;
}
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
}
Now I also have score.cs and mainMenu scene , but no need to include that for this question, What I am struggling with now is the AI not stopping after it scores a point, I want the AI paddle to stop for a few seconds before it serves.
As you can see in the code, I added a yield return new WaitForSecondsRealtime(2f);
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
But this only cause the paddle to wait for the first time it scores , and then if it scores again in quick interval, it doesn't wait at all.
Not sure what I doing wrong here, thanks for your time.
The more plausible explanation to me would be that GameHandler.rallyOn and isServing* aren't all being reset to the correct values to prevent the serve from occurring before the coroutine even begins, or another check is needed somewhere to prevent a serve from occurring if all of the correct booleans aren't set. Try placing appropriate breakpoints and stepping through in a debugger.
You'd probably be better off using WaitForSeconds rather than WaitForSecondsRealtime, by the way, at least if there's a chance you might want to allow the game to be paused in the future.
Realized that the coroutine is called multiple times even before its finished due do it being called from update function in GameHandler.cs. Needed a way to check if the co-routine is already running and not start a new one from update if so, used the Game.isServing variable to do so:
public IEnumerator serveAi()
{
GameHandler.isServingRight = false; // coroutine started , no need to start new one
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
}
And my update in GameHandler is already checking for that variable before starting the coroutine:
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
}
}
}
I'm working currently at a 2D Game for Android. There is a player in my scene and if the user tilts his device the player Object is moving on the ground. But he is just moving out of the screen at the left and the right side. I tried to make a "wall" but I had no success. At my player-Gameobject there is an edge collider. Now my question is: how can my player gameobject collide with the side of the screen?
This is my code:
public GameObject player;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Vector3 dir = Vector3.zero;
dir.y = Input.acceleration.x;
player.transform.Translate(new Vector2(dir.y, 0) * Time.deltaTime * 2000f);
}
Thank you very much! :)
Jul
EDIT:
Image 1 is my Wall's and Image 2 my Player's.
I'm trying to solve it with a wall at the side of the screen. These are the images of
Solved
Solution code:
Vector3 position = player.transform.position;
translation = Input.acceleration.x * movementSpeed * 50f;
if (player.transform.position.x + translation < LeftlimitScreen)
{
position.x = -LeftlimitScreen;
}
else if(transform.position.x + translation > RightlimitScreen)
{
position.x = RightlimitScreen;
}
else
{
position.x += translation;
player.transform.position = position;
}
This code is working for me! :)
This will generate edge colliders around the screen (for 2d):
void GenerateCollidersAcrossScreen()
{
Vector2 lDCorner = camera.ViewportToWorldPoint(new Vector3(0, 0f, camera.nearClipPlane));
Vector2 rUCorner = camera.ViewportToWorldPoint(new Vector3(1f, 1f, camera.nearClipPlane));
Vector2[] colliderpoints;
EdgeCollider2D upperEdge = new GameObject("upperEdge").AddComponent<EdgeCollider2D>();
colliderpoints = upperEdge.points;
colliderpoints[0] = new Vector2(lDCorner.x, rUCorner.y);
colliderpoints[1] = new Vector2(rUCorner.x, rUCorner.y);
upperEdge.points = colliderpoints;
EdgeCollider2D lowerEdge = new GameObject("lowerEdge").AddComponent<EdgeCollider2D>();
colliderpoints = lowerEdge.points;
colliderpoints[0] = new Vector2(lDCorner.x, lDCorner.y);
colliderpoints[1] = new Vector2(rUCorner.x, lDCorner.y);
lowerEdge.points = colliderpoints;
EdgeCollider2D leftEdge = new GameObject("leftEdge").AddComponent<EdgeCollider2D>();
colliderpoints = leftEdge.points;
colliderpoints[0] = new Vector2(lDCorner.x, lDCorner.y);
colliderpoints[1] = new Vector2(lDCorner.x, rUCorner.y);
leftEdge.points = colliderpoints;
EdgeCollider2D rightEdge = new GameObject("rightEdge").AddComponent<EdgeCollider2D>();
colliderpoints = rightEdge.points;
colliderpoints[0] = new Vector2(rUCorner.x, rUCorner.y);
colliderpoints[1] = new Vector2(rUCorner.x, lDCorner.y);
rightEdge.points = colliderpoints;
}
You can place in your scene, outside of the region which will be displayed in your device 2 empty game objects with a collider, so the player will crash against them.
You can also limit by code the boundaries within the player can move. You apply this using Mathf.Clamp(), and there you will need to set the boundaries in the x coordinate for your scene.
You will see that instead of modifying the position of the player using its transform, we use the rigidbody instead.
public class PlayerController : MonoBehaviour
{
public float speed;
public float tilt;
public Boundary boundary;
void FixedUpdate ()
{
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rigidbody.velocity = movement * speed;
rigidbody.position = new Vector3
(
Mathf.Clamp (rigidbody.position.x, boundary.xMin, boundary.xMax),
0.0f,
5.0f
);
}
}
You can check the whole tutorial here: https://unity3d.com/earn/tutorials/projects/space-shooter/moving-the-player?playlist=17147
Update Other options:
//You select here the speed you consider
float speed = 1.0f;
void Update () {
Vector3 dir = Vector3.zero;
float InputValue = Input.acceleration.x * speed;
//You need to set the values for this limits (max and min) based on your scene
dir.y = Mathf.Clamp(InputValue, 0.5f, 50.5f);
player.transform.position = dir;
}
Update 2:
Without Clamp, just setting the limits on the script
void Update () {
Vector3 position = player.transform.position ;
translation = Input.acceleration.x * speed;
if( player.transform.position.y + translation < leftLimitScreen )
position.y = -leftLimitScreen ;
else if( myTransform.position.x + translation > rightLimitScreen )
position.y = rightLimitScreen ;
else
position.y += translation ;
player.transform.position = position ;
}
In a prototype i'm creating the solution i had arrived was creating "walls" with objects without sprites in the borders and checking if there is something there with Raycast with a script like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
RaycastHit2D[] hit;
Vector2[] directions;
private Vector2 targetPosition;
private float moveSpeed;
private float moveHDir;
private float wallPos;
private bool hitLeft;
private bool hitRight;
// Use this for initialization
void Start () {
directions = new Vector2[2] {Vector2.right, Vector2.left};
hitLeft = false;
hitRight = false;
}
// Update is called once per physics timestamp
void FixedUpdate () {
foreach (Vector2 dir in directions) {
hit = Physics2D.RaycastAll(transform.position, dir);
Debug.DrawRay(transform.position, dir);
if (hit[1].collider != null) {
// Keyboard control
if (Input.GetAxisRaw("Horizontal") != 0) {
moveHDir = Input.GetAxisRaw("Horizontal");
// I have found that a 5% of the size of the object it's a
// good number to set as a minimal distance from the obj to the borders
if (hit[1].distance <= (transform.localScale.x * 0.55f)) {
if (dir == Vector2.left) {
hitLeft = true;
} else {
hitRight = true;
}
wallPos = hit[1].collider.transform.position.x;
// Condition that guarantee that the paddle do not pass the borders of the screen
// but keeps responding if you try to go to the other side
if ((wallPos > this.transform.position.x && moveHDir < 0) ||
(wallPos < this.transform.position.x && moveHDir > 0)) {
moveSpeed = gControl.initPlayerSpeed;
} else {
moveSpeed = 0;
}
} else {
if (dir == Vector2.left) {
hitLeft = false;
} else {
hitRight = false;
}
if (!hitRight && !hitLeft)
{
moveSpeed = gControl.initPlayerSpeed;
}
}
}
}
}
targetPosition = new Vector2((transform.position.x + (moveSpeed * moveHDir)), transform.position.y);
}
}
Maybe it's not the best solution or the shortest one, but it's working wonders to me.
Good luck.
In case if you want to generate collider on the borders of the canvas (2D)
Attach this script in the main canvas object.
using UnityEngine;
public class BorderCollider: MonoBehaviour
{
private EdgeCollider2D _edgeCollider2D;
private Rigidbody2D _rigidbody2D;
private Canvas _canvas;
private float y, x;
private Vector2 _topLeft, _topRight, _bottomLeft, _bottomRight;
private void Start() {
//Adding Edge Collider
_edgeCollider2D = gameObject.AddComponent<EdgeCollider2D>();
//Adding Rigid body as a kinematic for collision detection
_rigidbody2D = gameObject.AddComponent<Rigidbody2D>();
_rigidbody2D.bodyType = RigidbodyType2D.Kinematic;
//Assigning canvas
_canvas = GetComponent<Canvas>();
GetCanvasDimension(); // Finds height and width fo the canvas
GetCornerCoordinate(); // Finds co-ordinate of the corners as a Vector2
DrawCollider(); // Draws Edge collide around the corners of canvas
}
public void GetCornerCoordinate() {
// Assign corners coordinate in the variables
_topLeft = new Vector2(-x,y); // Top Left Corner
_topRight = new Vector2(x,y); // Top Right Corner
_bottomLeft = new Vector2(-x,-y); // Bottom Left Corner
_bottomRight = new Vector2(x,-y); // Bottom Right Corner
}
void GetCanvasDimension(){
y = (_canvas.GetComponent<RectTransform>().rect.height) / 2;
x = (_canvas.GetComponent<RectTransform>().rect.width) / 2;
}
void DrawCollider() {
_edgeCollider2D.points = new[] {_topLeft, _topRight, _bottomRight, _bottomLeft,_topLeft};
}
}
I created an editor tool that creates game objects in a circle, equally apart from each other (code below). It has a create button, so parameters are adjusted before the create is smashed. I would like to see changes made dynamically in the scene view rather. How can I adjust the following parameters with a slider rather:
Create or destroy game objects using an object count slider, whiles adjusting to keep the objects the same angle apart
dynamically adjust the game objects by changing a radius slider
rotate all objects on the circle (like spinning a wheel) using an angle slider
CircleSpawn
public class CircleSpawn : MonoBehaviour {
public float radius;
public int numOfItems;
public GameObject clonedObject;
public List<GameObject> spawnedObjects;
}
CircleSpawnEditor
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
public override void OnInspectorGUI()
{
var tar = (CircleSpawn)target;
//set its values
tar.radius = EditorGUILayout.FloatField("Radius:", tar.radius);
tar.numOfItems = EditorGUILayout.IntField("Number of Items:", tar.numOfItems);
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
//Inspector button for creating the objects in the Editor
if (GUILayout.Button("Create"))
{
//clean up old objects
if (tar.spawnedObjects != null)
{
foreach (var ob in tar.spawnedObjects)
{
DestroyImmediate(ob);
}
}
tar.spawnedObjects = new List<GameObject>();
float angleBetween = 360.0f / tar.numOfItems;
float angle = 0;
for (int i = 0; i <= tar.numOfItems; i++)
{
//for each object, find a rotation and position
var rot = Quaternion.Euler(0, 0, angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects.Add(Instantiate(tar.clonedObject,
tar.transform.position + localPos, rot));
angle += angleBetween;
}
}
}
}
Create or destroy game objects using an object count slider, whiles adjusting to keep the objects the same angle apart
Use an IntSlider for numOfItems and re-create the objects when:
numOfItems != spawnedObjects.Count
Dynamically adjust the game objects by changing a radius slider
Use a Slider for radius, and when it changes, iterate over spawnedObjects and move them by:
pos = rot * Vector3.right * tar.radius
Rotate all objects on the circle (like spinning a wheel) using an angle slider
Use a Slider for spin, and when it changes, iterate over spawnedObjects and rotate them by:
rot = Quaternion.Euler(0, 0, tar.spin + angle)
CircleSpawn:
public class CircleSpawn : MonoBehaviour
{
public float radius, radiusLast, spin, spinLast;
public int numOfItems;
public GameObject clonedObject;
public List<GameObject> spawnedObjects;
}
CircleSpawnEditor:
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
public override void OnInspectorGUI ()
{
var tar = (CircleSpawn)target;
EditorGUILayout.LabelField("Radius"); // Set as required
tar.radius = EditorGUILayout.Slider(tar.radius, 0f, 100f);
EditorGUILayout.LabelField("Angle"); // Set as required
tar.spin = EditorGUILayout.Slider(tar.spin, 0f, 360f);
EditorGUILayout.LabelField("Number of Items"); // Set as required
tar.numOfItems = EditorGUILayout.IntSlider(tar.numOfItems, 0, 36);
EditorGUILayout.LabelField("Object");
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
float angle, angleBetween = 360.0f / tar.numOfItems;
if (tar.spawnedObjects == null)
tar.spawnedObjects = new List<GameObject>();
// Solution #1
if (tar.spawnedObjects.Count != tar.numOfItems)
{
foreach (var ob in tar.spawnedObjects)
DestroyImmediate(ob);
tar.spawnedObjects.Clear();
angle = 0f;
for (int i = 0; i < tar.numOfItems; i++)
{
var rot = Quaternion.Euler(0f, 0f, tar.spin + angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects.Add(Instantiate(tar.clonedObject,
tar.transform.position + localPos, rot));
angle += angleBetween;
}
}
// Solutions #2 & 3
if (!Mathf.Approximately(tar.spin, tar.spinLast) ||
!Mathf.Approximately(tar.radius, tar.radiusLast))
{
tar.spinLast = tar.spin;
tar.radiusLast = tar.radius;
angle = 0f;
for (int i = 0; i < tar.numOfItems; i++)
{
var rot = Quaternion.Euler(0f, 0f, tar.spin + angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects[i].transform.position =
tar.transform.position + localPos;
tar.spawnedObjects[i].transform.rotation = rot;
angle += angleBetween;
}
}
}
}
There are few problems in your code:
1.Notice how there is one extra prefab created. Basically the number of items created is numOfItems + 1 instead of the value of numOfItems.
That's because of this line:
for (int i = 0; i <= tar.numOfItems; i++)
That should be i < tar.numOfItems not i <= tar.numOfItems;.
2.You are not clearing the List. After destroying the items, clear the list too with tar.Clear();. If you don't do this, you will have list with null items that will keep growing.
To accomplish what's in your question, you will need a way to detect when the radius and numOfItems value changes. You need something similar to the Update function. There is none if you derive your script from Editor instead of MonoBehaviour.
To get something similar to the Update function event while deriving your script from Editor, subscribe to the EditorApplication.update event in the OnEnable function and unsubscribe from it in the OnDisable function. This should handle your #1 and #2 questions
To rotate all of them at the-same time, use the transform.RotateAround function. This should handle your #3 question.
I spent time yesterday to see how easy this is and following everything I mentioned above, below is what I came up with:
Below is the code. It can be improved. Most things are repeatable so I tried as much to put them in a function and used enum to determine which things to execute depending on the slider. Things shortened the code almost twice the original size. You don't have to use this code directly but you can use it to learn what I did and make yours if you prefer:
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
private CircleSpawn targetObj;
private static float previousRadius;
private static int previousNumOfItems;
private static float previousAngleIncrement;
private static float angleIncrement = 0;
public override void OnInspectorGUI()
{
var tar = (CircleSpawn)target;
targetObj = tar;
makeSlider("Radius", tar.radius, 0f, 10, 24, UpdateType.RADIUS);
makeSlider("Number of Items", tar.numOfItems, 0f, 100, 24, UpdateType.RESPAWN);
makeSlider("Angle", angleIncrement, 0f, 360, 24, UpdateType.ANGLE);
//set its values
tar.radius = EditorGUILayout.FloatField("Radius:", tar.radius);
tar.numOfItems = EditorGUILayout.IntField("Number of Items:", tar.numOfItems);
angleIncrement = EditorGUILayout.FloatField("Angle:", angleIncrement);
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
//Inspector button for creating the objects in the Editor
if (GUILayout.Button("Create"))
{
RespawnNumOfItems();
}
}
void makeSlider(string label, float value, float min, float max, int space, UpdateType updateType)
{
GUILayout.Space(2);
GUILayout.Label(label);
if (updateType == UpdateType.RADIUS)
targetObj.radius = GUILayout.HorizontalSlider(targetObj.radius, min, max);
if (updateType == UpdateType.RESPAWN)
targetObj.numOfItems = (int)GUILayout.HorizontalSlider(targetObj.numOfItems, min, max);
if (updateType == UpdateType.ANGLE)
angleIncrement = GUILayout.HorizontalSlider(angleIncrement, min, max);
GUILayout.BeginHorizontal();
var defaultAlignment3 = GUI.skin.label.alignment;
GUILayout.Label(min.ToString());
GUI.skin.label.alignment = TextAnchor.UpperRight;
GUILayout.Label(max.ToString());
GUI.skin.label.alignment = defaultAlignment3;
GUILayout.EndHorizontal();
GUILayout.Space(space);
}
void OnEnable()
{
EditorApplication.update += Update;
}
void OnDisable()
{
EditorApplication.update -= Update;
}
void Update()
{
if (targetObj != null)
{
float clampedNewRadius = Mathf.Clamp(targetObj.radius, 0f, float.MaxValue);
//Check if Radius changed
if (RadiusChanged())
{
//Debug.Log("Radius Changed: " + targetObj.radius);
previousRadius = clampedNewRadius;
targetObj.radius = clampedNewRadius;
UpdateRadius();
}
int clampedNewNumOfItems = Mathf.Clamp(targetObj.numOfItems, 0, int.MaxValue);
//Check if NumOfItems changed
if (NumOfItemsChanged())
{
//Debug.Log("NumOfItems Changed: " + previousNumOfItems);
previousNumOfItems = clampedNewNumOfItems;
targetObj.numOfItems = clampedNewNumOfItems;
RespawnNumOfItems();
}
float clampedAngle = Mathf.Clamp(angleIncrement, 0, int.MaxValue);
//Check if Angle changed
if (AngleChanged())
{
//Debug.Log("Angle Changed: " + previousAngleIncrement);
UpdateAngle();
previousAngleIncrement = clampedAngle;
angleIncrement = clampedAngle;
}
}
}
private void UpdateAngle()
{
UpdateTransform(UpdateType.ANGLE);
}
private void RespawnNumOfItems()
{
if (targetObj.spawnedObjects == null)
targetObj.spawnedObjects = new List<GameObject>();
//clean up old objects
if (targetObj.spawnedObjects != null)
{
// Debug.LogWarning("Destroyed");
foreach (var ob in targetObj.spawnedObjects)
{
DestroyImmediate(ob);
}
}
//Clear list
targetObj.spawnedObjects.Clear();
//Debug.LogWarning("Cleared List");
UpdateTransform(UpdateType.RESPAWN);
}
private void UpdateRadius()
{
UpdateTransform(UpdateType.RADIUS);
}
void UpdateTransform(UpdateType updateType)
{
float angleBetween = 360.0f / targetObj.numOfItems;
float angle = 0;
if (targetObj != null)
for (int i = 0; i < targetObj.numOfItems; i++)
{
//For each object, find a rotation and position
var rot = Quaternion.Euler(0, 0, angle);
var localPos = rot * Vector3.right * targetObj.radius;
//Debug.LogWarning("Updated");
if (updateType == UpdateType.RADIUS)
{
//Make sure that loop is within range
if (targetObj.spawnedObjects != null && i < targetObj.spawnedObjects.Count)
{
GameObject obj = targetObj.spawnedObjects[i];
if (obj != null)
{
Transform trans = obj.transform;
trans.position = targetObj.transform.position + localPos;
trans.rotation = rot;
}
}
}
else if (updateType == UpdateType.RESPAWN)
{
if (targetObj.clonedObject != null)
{
GameObject objToAdd = Instantiate(targetObj.clonedObject, Vector3.zero, Quaternion.identity);
objToAdd.transform.position = targetObj.transform.position + localPos;
objToAdd.transform.rotation = rot;
targetObj.spawnedObjects.Add(objToAdd);
}
else
{
// Debug.LogError("Please assign the clonedObject prefab in the Scene");
}
}
else if (updateType == UpdateType.ANGLE)
{
//Make sure that loop is within range
if (targetObj.spawnedObjects != null && i < targetObj.spawnedObjects.Count)
{
GameObject obj = targetObj.spawnedObjects[i];
if (obj != null)
{
Transform trans = obj.transform;
Vector3 tagetPoint = targetObj.transform.position;
//Decide if we should rotate left or rigt
if (previousAngleIncrement > angleIncrement)
trans.RotateAround(tagetPoint, Vector3.forward, angleIncrement);
else
trans.RotateAround(tagetPoint, -Vector3.forward, angleIncrement);
}
}
}
if (updateType != UpdateType.ANGLE)
angle += angleBetween;
}
//Uncomment to test auto angle rotation over frame
//testAngle();
}
void testAngle()
{
float speed = 0.005f;
angleIncrement = (float)EditorApplication.timeSinceStartup * speed;
}
private bool RadiusChanged()
{
bool changed = !Mathf.Approximately(targetObj.radius, previousRadius)
&& !(targetObj.radius < 0);
return changed;
}
private bool NumOfItemsChanged()
{
bool changed = (targetObj.numOfItems != previousNumOfItems)
&& !(targetObj.numOfItems < 0);
return changed;
}
private bool AngleChanged()
{
bool changed = !Mathf.Approximately(angleIncrement, previousAngleIncrement)
&& !(angleIncrement < 0);
return changed;
}
public enum UpdateType
{
RADIUS, RESPAWN, ANGLE
}
}
I am attempting to create a minigolf game but am having trouble solving how to calculate the direction to hit the ball. I am thinking that it is easiest to hit the ball in the forward direction that the camera is facing, but I get unexpected results after the first hit as the ball will not calculate the correct direction after hitting the first time. How can I set the direction for hitting the ball and then apply the force I am calculating?
This is my script attached to my ball object at the moment. Sorry for the messiness.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Golfball : MonoBehaviour {
public GameObject ball = null;
public GameObject hole = null;
public GameObject cam = null;
public Text distance;
public Text score;
public Slider powerbar;
private int strokes = 0;
private bool isMoving = false;
private bool increasing = true;
private float distanceToHole;
public float minHitPower = 40.0f;
public float maxHitPower = 270.0f;
private float hitPower = 0;
private float powerIncrement = 5.0f;
private float powerMultiplier = 10;
private float ballRollTime = 0;
private Vector3 ballDir;
// Use this for initialization
void Start() {
distance.GetComponent<Text>().text = "Distance To Hole:" + distanceToHole;
ball.GetComponent<Rigidbody> ();
score.GetComponent<Text>().text = "Strokes:" + strokes;
}
// Update is called once per frame
void Update () {
//Allow the ball to be hit if the ball is not null, not currently moving, and the left mouse button is clicked.
if (ball != null) {
if (Input.GetButton("Fire1") && !isMoving) {
calculatePower ();
}
//Hit ball using power level and set ball to moving.
if (Input.GetButtonUp("Fire1"))
{/**********************************************
//Calculate direction to hit ball
ballDir = cam.transform.forward.normalized;
hitBall(hitPower);
isMoving = true;
}**********************************************/
//Detect when the ball stops
if (isMoving) {
ballRollTime += Time.deltaTime;
if (ballRollTime > 1 && GetComponent<Rigidbody> ().velocity.magnitude <= 0.5) {
GetComponent<Rigidbody> ().velocity = Vector3.zero;
GetComponent<Rigidbody> ().angularVelocity = Vector3.zero;
isMoving = false;
}
} else {
ballRollTime = 0;
}
}
//Calculate distance to hole
distanceToHole = Mathf.Round((Vector3.Distance(ball.transform.position, hole.transform.position) * 100f) / 100f);
distance.GetComponent<Text>().text = "Distance To Hole: " + distanceToHole;
Debug.DrawLine(ball.transform.position, ballDir, Color.red, Mathf.Infinity);
}
void calculatePower(){
//Increase power if it is less than the max power.
if (increasing)
{
if (hitPower < maxHitPower)
{
hitPower += powerIncrement * powerMultiplier;
increasing = true;
}
else if (hitPower >= maxHitPower)
{
increasing = false;
}
}
//Decrease power if power level is not increasing until the power hits the minimum level.
if(!increasing) {
//Debug.Log ("Not Increasing");
if (hitPower > minHitPower) {
//Debug.Log ("HitPower: " + hitPower);
hitPower -= powerIncrement * powerMultiplier;
} else if (hitPower <= minHitPower) {
increasing = true;
}
}
//Update the slider
powerbar.value = hitPower / powerMultiplier;
}
void hitBall (float power){
//Add force to the ball
//ball.GetComponent<Rigidbody>().AddForce(new Vector3(0, 0, power));
//Camera.main.transform.forward
ball.GetComponent<Rigidbody>().AddRelativeForce(ballDir * power);
//Increase stroke count
strokes++;
updateScore(strokes);
//Reset the power and power bar level to minimum default after hitting ball
hitPower = minHitPower;
powerbar.value = hitPower / powerMultiplier;
Debug.Log("HitPower Reset: " + hitPower);
}
void updateScore(int stroke)
{
score.GetComponent<Text>().text = "Strokes:" + stroke;
}
}
You'll have to apply the force like that:
ball.GetComponent<Rigidbody>().AddForce(ballDir * power, ForceMode.Impulse);
But you'll probably have to play with your 'power' variable.
Also note that you want to use impulse because you are hitting the ball and not applying force for some period of time.
The ballDir * power multiples the ballDir vector by your float number. You probably already know how it works, but here is the rundown:
scalarValue * new Vector(x0, x1, ..., xn) == new Vector(scalarValue * x0, scalarValue * x1, ... scalarValue * xn);]
You might also want to get rid of direction's y component so that your ball doesn't fly into the air (you are playing minigolf, right?)
ballDir.y = 0;
I have a scene in which the player can pick up and drop objects, as well as move and look around.
All player objects are children of an empty game object "MainCharacter":
MainCharacter >
Capsule (With RigidBody and PlayerMoveScript) >
PlayerBase (empty - used for checking if grounded)
MainCamera >
Hands(With PickUpDrop script)
The object I pick up Lerps to my Hands position, however after my capsule collides with any walls there is a strange jittering which I cannot work out how to fix!!
Heres the .exe:GameTest
Heres the data folder : Data
Here are the scripts:
Pick Up and Drop Script:
public bool handsFull = false;
public float distanceMax = 20f;
public Transform handPosition;
public LayerMask canPickUp;
public GameObject taggedGameObject;
public bool colliderTriggered;
public bool bounds;
public PickedUpObject pickedUpScript;
public Rigidbody player;
// Use this for initialization
void Start () {
print(FindClosestPickup().name);
handPosition = transform;
pickedUpScript = null;
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown (KeyCode.E) && !bounds) {
if (Physics.CheckSphere (handPosition.position, 2f, canPickUp)) {
if (handsFull) {
Drop ();
}
if (!handsFull) {
PickedUp ();
}
handsFull = !handsFull;
}
}
if (handsFull) {
RotateMovePickedUpObject();
}
}
private void PickedUp(){
//Closest object to top of list
taggedGameObject = (GameObject)FindClosestPickup();
taggedGameObject.collider.isTrigger = true;
taggedGameObject.rigidbody.useGravity = false;
taggedGameObject.rigidbody.isKinematic = true;
pickedUpScript = taggedGameObject.GetComponent<PickedUpObject> ();
Debug.Log ("Pick Up");
}
private void RotateMovePickedUpObject(){
//Rotate
if(Input.GetKeyDown(KeyCode.End)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(0, 0, 45);
}
if(Input.GetKeyDown(KeyCode.Delete)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(0, 45, 0);
}
if(Input.GetKeyDown(KeyCode.PageDown)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(0, -45, 0);
}
if(Input.GetKeyDown(KeyCode.Home)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(0, 0, -45);
}
if(Input.GetKeyDown(KeyCode.PageUp)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(-45, 0, 0);
}
if(Input.GetKeyDown(KeyCode.Insert)){
taggedGameObject.transform.localRotation *= Quaternion.Euler(45, 0, 0);
}
taggedGameObject.transform.position = Vector3.Lerp(taggedGameObject.transform.position, handPosition.position, (1 - Mathf.Exp( -20 * Time.smoothDeltaTime )) * 10);
}
private void Drop(){
taggedGameObject.collider.isTrigger = false;
taggedGameObject.rigidbody.useGravity = true;
taggedGameObject.rigidbody.isKinematic = false;
taggedGameObject = null;
Debug.Log ("Drop");
pickedUpScript = null;
}
private GameObject FindClosestPickup() {
//Find closest gameobject with tag
GameObject[] gos;
gos = GameObject.FindGameObjectsWithTag("pickup");
GameObject closest = null;
float distance = Mathf.Infinity;
Vector3 position = transform.position;
foreach (GameObject go in gos) {
Vector3 diff = go.transform.position - position;
float curDistance = diff.sqrMagnitude;
if (curDistance < distance) {
closest = go;
distance = curDistance;
}
}
return closest;
}
}
The Picked Up Objects Script:
public PickUpDrop pickUpScript;
public GameObject thisOne;
public Color thecolor;
public bool inObject;
// Use this for initialization
void Start () {
thisOne = this.gameObject;
}
// Update is called once per frame
void Update ()
{
thecolor = thisOne.renderer.material.color;
if (pickUpScript.taggedGameObject != thisOne)
{
gameObject.renderer.material.color = Color.gray;
}
if (pickUpScript.taggedGameObject == thisOne)
{
Color color = renderer.material.color;
color.a = 0.5f;
renderer.material.color = color;
}
}
void OnTriggerEnter ()
{
if (thisOne == pickUpScript.taggedGameObject)
{
inObject = true;
pickUpScript.bounds = true;
gameObject.renderer.material.color = Color.red;
}
}
void OnTriggerExit()
{
if(thisOne == pickUpScript.taggedGameObject)
{
inObject = false;
pickUpScript.bounds = false;
gameObject.renderer.material.color = Color.gray;
}
}
}
taggedGameObject.transform.position = Vector3.Lerp(taggedGameObject.transform.position, handPosition.position, (1 - Mathf.Exp( -20 * Time.smoothDeltaTime )) * 10);
This line will keep moving the object towards the hand's position. If you have a rigidbody attached to the game object you're moving then the physics acting on that object during the physics calculation will conflict with the manual movement of the object during the Update function.
It depends on what you would like to happen when this conflict occurs as to the solution. If you simply want the 'jittering' to stop and still be able to hold objects against other physical objects, then use this;
taggedGameObject.rigidbody.AddForce( ( taggedGameObject.transform.position - handPosition.position ) * force );
This will keep all interactions with the rigidbody. You'll have to tweak the force you move the object with, and perhaps disable gravity on the tagged game object while it's in the players hands. But it should have the desired effect.