I have this class, I use it for drawing sprites in my 2D game:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
namespace Namespace
{
/// <summary>
/// Found this brilliant code at.
/// http://adambruenderman.wordpress.com/2011/04/05/create-a-2d-camera-in-xna-gs-4-0/
/// </summary>
public class CCamera2d
{
private const float zoomUpperLimit = 1.3f;
private const float zoomLowerLimit = .85f;
private float _zoom;
private Matrix _transform;
private Vector2 _pos;
private float _rotation;
private int _viewportWidth;
private int _viewportHeight;
private int _worldWidth;
private int _worldHeight;
public CCamera2d(Viewport viewport, int worldWidth, int worldHeight, float initialZoom = zoomLowerLimit)
{
_zoom = initialZoom;
_rotation = 0.0f;
_pos = Vector2.Zero;
_viewportWidth = viewport.Width;
_viewportHeight = viewport.Height;
_worldWidth = worldWidth;
_worldHeight = worldHeight;
}
#region Properties
public float Zoom
{
get { return _zoom; }
set
{
_zoom = value;
if (_zoom < zoomLowerLimit)
_zoom = zoomLowerLimit;
if (_zoom > zoomUpperLimit)
_zoom = zoomUpperLimit;
}
}
public float Rotation
{
get { return _rotation; }
set { _rotation = value; }
}
public void Move(Vector2 amount)
{
_pos += amount;
}
public Vector2 Pos
{
get { return _pos; }
set
{
float leftBarrier = (float)_viewportWidth *
.5f / _zoom;
float rightBarrier = _worldWidth -
(float)_viewportWidth * .5f / _zoom;
float topBarrier = _worldHeight -
(float)_viewportHeight * .5f / _zoom;
float bottomBarrier = (float)_viewportHeight *
.5f / _zoom;
_pos = value;
if (_pos.X < leftBarrier)
_pos.X = leftBarrier;
if (_pos.X > rightBarrier)
_pos.X = rightBarrier;
if (_pos.Y > topBarrier)
_pos.Y = topBarrier;
if (_pos.Y < bottomBarrier)
_pos.Y = bottomBarrier;
}
}
#endregion
public Matrix GetTransformation()
{
_transform =
Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
Matrix.CreateTranslation(new Vector3(_viewportWidth * 0.5f,
_viewportHeight * 0.5f, 0));
return _transform;
}
}
}
Now, I can't understand how to unproject a screen (mouse) position to world position.
It would be great to have a function like this:
Vector2 ScreenToWorld(Vector2 onScreen, Matrix CameraTransformation)
For WorldToScreen I use (works fine) function:
public Vector2 WorldToScreen(Vector2 inWorld)
{
return Vector2.Transform(inWorld, Camera.CurrentTransformation);
}
Inverted matrix:
public Vector2 ScreenToWorld(Vector2 onScreen)
{
var matrix = Matrix.Invert(World.Camera.CurrentTransformation);
return Vector2.Transform(onScreen, matrix);
}
Related
Hello I want to Make the gun of my turret face the direction my camera is facing at.
I don't really know Complex Maths so can you keep it simple. Thanks
My code is this:
gun.localEulerAngles = new Vector3(tankCamera.eulerAngles.x - transform.eulerAngles.x, 0);
Here is the Complete Class:
public class TurretController : MonoBehaviour
{
public Transform turret;
public Transform gun;
public Transform tankCamera;
[Header("Values")]
public float lowestX = 23;
public float highestX = -9;
public float turnSpeed = 10f;
public float additionalX = 90f;
public float maxRadians = 90f;
public bool gunLocal = true;
public bool turretRotation = false;
public LayerMask layerMaskOfGun;
[Header("Read-Only")] [SerializeField] private Vector3 angle;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (turretRotation)
{
turret.localRotation = Quaternion.RotateTowards(turret.localRotation,
new Quaternion(0, tankCamera.rotation.y, 0, turret.localRotation.w),
Time.deltaTime * turnSpeed);
}
else
{
angle = new Vector3(0, (tankCamera.eulerAngles.y - transform.eulerAngles.y));
//turret.localEulerAngles = Vector3.Lerp(turret.localEulerAngles, angle, Time.deltaTime / turnSpeed);
turret.localEulerAngles = angle;
}
//if (gun.localRotation.x <= lowestX || gun.localRotation.x >= highestX)
{
if (gunLocal)
{
gun.localEulerAngles = new Vector3(tankCamera.eulerAngles.x - transform.eulerAngles.x, 0);
//gun.localRotation = new Quaternion(tankCamera.rotation.x, 0f, 0f, gun.rotation.w);
}
else
{
Camera camera;
//RaycastHit hit;
//if (Physics.Raycast(tankCamera.position, tankCamera.forward, out hit, -layerMaskOfGun))
{
//gun.LookAt(hit.point);
gun.LookAt(tankCamera.forward);
}
}
}
}
}
I want to change between 2 materials, depending on the platforms (gameobject) rotation.
Here is what I've done so far:
public class PlatformSpawner : MonoBehaviour
{
public GameObject platform;
public Material[] platformMaterial;
Material currentMaterial;
Renderer _renderer;
}
void Start()
{
_renderer = this.GetComponent<Renderer>();
}
I also wrote this, but I don't want to change materials by buttons:
public void LeftTurn()
{
_renderer.material = platformMaterial[0];
currentMaterial = _renderer.material;
}
public void RightTurn()
{
_renderer.material = platformMaterial[1];
currentMaterial = _renderer.material;
}
}
And this is where the platform rotates randomly 90 degrees to the left or to the right:
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public void Step(float distance)
{
if (Random.value < 0.5)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0); //change to one of the materials
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0); //change to the other of the materials.
//This is where I want to material to switch.
//When the objects position changes, the material changes too.
}
}
}
There is a picture of the gameplay. I want to change the material of all the corner platforms to have a nice curve line view.
Can anyone help me what and how to do in this case? I am a bit lost there.
Every help is highly appreciated!
EDIT: new code looks like that. The only issue is that Unity gives me 15 errors (see on the picture below), even if Visual Studio says no issue has been found. The errors refer to the switch.
public class PlatformSpawner : MonoBehaviour
{
public GameObject platform;
public Transform lastPlatform;
SpawnPoint _spawn;
bool stop;
public Material straightMaterial;
public Material turnLeftMaterial;
public Material turnRightMaterial;
public Renderer roadPrefab;
[System.Serializable]
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public RoadType type;
public enum RoadType
{
Straight,
LeftTurn,
RightTurn
}
private enum Direction
{
Z,
X,
}
private Direction lastDirection;
public void Step(float distance)
{
type = RoadType.Straight;
if (Random.value < 0.5f)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0);
if (lastDirection == Direction.Z)
{
type = RoadType.RightTurn;
}
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0);
if (lastDirection == Direction.X)
{
type = RoadType.LeftTurn;
}
lastDirection = Direction.Z;
}
}
}
void Start()
{
_spawn.position = lastPlatform.position;
_spawn.orientation = transform.rotation;
StartCoroutine(SpawnPlatforms());
}
IEnumerator SpawnPlatforms()
{
while (!stop)
{
var _spawn = new SpawnPoint();
for (var i = 0; i < 20; i++)
{
var newPlatform = Instantiate(roadPrefab, _spawn.position, _spawn.orientation);
_spawn.Step(1.5f);
var roadMaterial = _spawn.type switch
{
SpawnPoint.RoadType.LeftTurn => turnLeftMaterial,
SpawnPoint.RoadType.RightTurn => turnRightMaterial,
_ => straightMaterial
};
newPlatform.GetComponent<Renderer>().material = roadMaterial;
yield return new WaitForSeconds(0.1f);
}
}
}
}
So it sounds like you basically have a working system for switching the materials and spawning you road parts and materials already look correctly according to your rotations - now you only need to identify the curves.
Actually this is pretty simple:
is the current part in X direction and the next will be in Z -> Left Turn
is the current part in Z direction and the next will be in X -> RightTurn
any other case is straight
So you could probably do something like
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public RoadType type;
public enum RoadType
{
Straight,
LeftTurn,
RightTurn
}
private enum Direction
{
// since your orientation by default equals the Z direction use that as default value for the first tile
Z,
X,
}
private Direction lastDirection;
public void Step(float distance)
{
type = RoadType.Straight;
if (Random.value < 0.5f)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0);
if(lastDirection == Direction.Z)
{
type = RoadType.RightTurn;
}
lastDirection = Direction.X;
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0);
if(lastDirection == Direction.X)
{
type = RoadType.LeftTurn;
}
lastDirection = Direction.Z;
}
}
}
And you didn't show your spawn code but I would assume something like
public class Example : MonoBehaviour
{
public Material straightMaterial;
public Material turnLeftMaterial;
public Material turnRightMaterial;
public Renderer roadPrefab;
private void Awake()
{
var spawnPoint = new SpawnPoint();
for(var i = 0; i < 20; i++)
{
var roadTile = Instantiate(roadPrefab, spawnPoint.position, spawnPoint.orientation);
// do the Step after spawning the current tile but before assigning the material
// -> we want/need to know already where the next tile is going to be
spawnPoint.Step(1f);
var roadMaterial = spawnPoint.type switch
{
SpawnPoint.RoadType.LeftTurn => turnLeftMaterial,
SpawnPoint.RoadType.RightTurn => turnRightMaterial,
_ => straightMaterial
};
roadTile.GetComponent<Renderer>().material = roadMaterial;
}
}
}
Behold my Paint skills ;)
This will get you started using Quaternion.Dot.
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class NewBehaviourScript : MonoBehaviour
{
public Material Material1;
public Material Material2;
public Vector3 Euler = new(90, 0, 0);
private Renderer _renderer;
private void Start()
{
_renderer = GetComponent<Renderer>();
_renderer.material = Material1;
}
private void Update()
{
var dot = Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler));
if (Mathf.Approximately(dot, 1.0f))
{
_renderer.material = Material2;
}
else
{
_renderer.material = Material1;
}
}
}
Using different materials for N, E, S, W corners:
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class NewBehaviourScript : MonoBehaviour
{
public Material Material1;
public Material Material2;
public Material Material3;
public Material Material4;
public Vector3 Euler1 = new(0, 0, 0);
public Vector3 Euler2 = new(0, 90, 0);
public Vector3 Euler3 = new(0, 180, 0);
public Vector3 Euler4 = new(0, 270, 0);
private Renderer _renderer;
private void Start()
{
_renderer = GetComponent<Renderer>();
_renderer.material = Material1;
}
private void Update()
{
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler1)), 1.0f))
{
_renderer.material = Material1;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler2)), 1.0f))
{
_renderer.material = Material2;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler3)), 1.0f))
{
_renderer.material = Material3;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler4)), 1.0f))
{
_renderer.material = Material4;
}
}
}
Make sure to wrap the rotation past 360 degrees, else it'll always look yellow (4th material).
I am currently programming a game in which an infinite procedural city is generated. so far everything works but because it leads to laggs if there are too many objects in the scene I wanted to make a script in which objects only appear near the player. I watched this video for help:https://www.youtube.com/watch?v=xlSkYjiE-Ck. When I tried to link this to my script (GenerateBuilding script) this error came:ArgumentException:
An item with the same key has already been added. Key: (0.0, 1.0)
System.Collections.Generic.Dictionary...
I need help to make the script work in which the houses are generated as well as the planes do, they should only be showed when the player is nearby
---Relevant Lines---
(Endless City)
calling updateChunk function in update()(updateChunk/building function is in GenerateBuilding script)
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
adding to dictionary line 68-80(UpdateVisibleChunks function)
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
EndlessCity, CityChunk class
CityChunk function, sending position to GenerateBuilding script to instantiate buildings in right position.
building.requestBuildingSquad(positionV3);
GenerateBuilding relevant lines
builderH function, instantiates the buildings
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
AddTest function, adds instantiates objects from builderH to a dictionary
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
Testing class, testing function, adds objects to class
public Testing(GameObject obj)
{
MeshObject = obj;
}
that should be all relevant lines
full scripts(really similar)
EndlessCity Script(this scripts generates the planes and gives position for GenerateBuilding script)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class EndlessCity : MonoBehaviour
{
public const float maxViewDst = 10;
public Transform viewer;
private GenerateBuilding building;
public static Vector2 viewerPosition;
int chunkSize;
int chunksVisibleInViewDst;
Dictionary<Vector2, CityChunk> terrainChunkDictionary = new Dictionary<Vector2, CityChunk>();
List<CityChunk> terrainChunksVisibleLastUpdate = new List<CityChunk>();
void Start()
{
chunkSize = 8 - 1;
chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
}
void Update()
{
viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
UpdateVisibleChunks();
}
void UpdateVisibleChunks()
{
building = FindObjectOfType<GenerateBuilding>();
building.UpdateBuildings();
for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
{
terrainChunksVisibleLastUpdate[i].SetVisible(false);
}
terrainChunksVisibleLastUpdate.Clear();
int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);
for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
{
for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
{
Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
{
terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
if (terrainChunkDictionary[viewedChunkCoord].IsVisible())
{
terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
}
}
else
{
terrainChunkDictionary.Add(viewedChunkCoord, new CityChunk(viewedChunkCoord, chunkSize, transform));
}
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
}
}
}
public class CityChunk
{
private GenerateBuilding building;
public GameObject meshObject;
public Vector3 positionV3;
Vector2 position;
Bounds bounds;
public CityChunk(Vector2 coord, int size, Transform parent)
{
building = FindObjectOfType<GenerateBuilding>();
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
positionV3 = new Vector3(position.x, 0, position.y);
int xPosition = building.xLength / 2;
int zPosition = building.zLength / 2;
float xOfsset = building.xOfsset;
float zOfsset = building.zOfsset;
float spaceBetween = building.spaceBetween;
//Instantiate plane
meshObject = Instantiate(building.groundObject, positionV3 + new Vector3((xPosition + xOfsset) * spaceBetween, -.5f, (xPosition + 1 + zOfsset) * spaceBetween), Quaternion.identity);
SetVisible(false);
building.requestBuildingSquad(positionV3);
}
public void UpdateTerrainChunk()
{
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
public void SetVisible(bool visible)
{
meshObject.SetActive(visible);
}
public bool IsVisible()
{
return meshObject.activeSelf;
}
}
}
GenerateBuilding(this script generates Buildings on the planes)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBuilding : MonoBehaviour
{
public int minHeight = 2;
public int maxHeight = 8;
public int cubeTileX;
public int cubeTileZ;
public int xLength;
public int zLength;
public float spaceBetween;
public float xOfsset;
public float zOfsset;
public GameObject TesObject;
public GameObject[] Base;
public GameObject[] secondB;
public GameObject[] roof;
public GameObject groundObject;
public List<GameObject> objectsss;
public Dictionary<Vector2, Testing> cityChunkDictionary = new Dictionary<Vector2, Testing>();
public List<Testing> buildingObjects = new List<Testing>();
public GameObject Test;
void Start()
{
//requestBuildingSquad(this.transform.position);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//
}
}
public void requestBuildingSquad(Vector3 position)
{
//*getting the middle of the city squad
int xPosition = xLength / 2;
int zPosition = zLength / 2;
//*
for (int z = 0; z < zLength; z++)
{
zOfsset++;
for (int x = 0; x < xLength; x++)
{
GenerateBuildings(position);
}
xOfsset = 0;
}
zOfsset = 0;
}
public void GenerateBuildings(Vector3 position)
{
int bHeight = Random.Range(minHeight, maxHeight);
float bOfsset = 0;
bOfsset += builderH(Base, bOfsset, position);
for (int i = 0; i < bHeight; i++)
{
bOfsset += builderH(secondB, bOfsset, position);
}
bOfsset += builderH(roof, bOfsset, position);
xOfsset++;
}
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
public class Testing
{
public GameObject MeshObject;
Vector2 position;
Bounds bounds;
public Testing(GameObject obj)
{
MeshObject = obj;
}
public void SetVisible(bool visiblee)
{
MeshObject.SetActive(visiblee);
}
public bool IsVisible()
{
return MeshObject.activeSelf;
}
public void UpdateCityChunk(Vector3 viewerPosition, Vector2 coord, int size, float maxViewDst)
{
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
}
}
The problem is that you are trying to add twice elements with the same key.
here is the documentation of the Add method for dictionaries, and as it states, trying to add an existing key throws an error.
You can either use the TryAdd method, which adds an item only if the key doesn't exist already in the dictionary, or update the value with the existing key as you can see here.
I want to instantiate GameObjects(specifically hexagonal tiles) at the hexagonalCoodinates(hexcoordinates).
For this I wrote a custom coordinate system.
But I found out that unity doesn't accept anything other than Vector3 or transform.
How do I make it do that?
Or is there a easier way to do this?
This is the method to generate the gameObjects
private void TouchCell(Vector3 point)//This method instantiates cubes
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
Instantiate(cubes, coordinates, Quaternion.identity);//<-The coordinate variable here is a hex coordinate.
Debug.Log("Touched at:" + coordinates);
}
And This is the Hex coordinate generator:
public struct HexCoordinates
{
public int X { get; private set; }
public int Z { get; private set; }
public int Y { get
{
return -X - Z;
} }
public HexCoordinates(int x,int z)
{
X = x;
Z = z;
}
public static HexCoordinates FromOffsetCoordinates(int x,int z)
{
return new HexCoordinates(x-z/2, z);
}
public override string ToString()
{
return "("+X.ToString()+","+Y.ToString()+","+Z.ToString()+")";
}
public string ToStringOnSeperateLines()
{
return X.ToString() + "\n" +Y.ToString()+ "\n" + Z.ToString();
}
public static HexCoordinates FromPosition(Vector3 point)//This converts the Vector3 to Hex coords
{
float x = point.x / (HexMetrics.InnerRadius * 2f);
float y = -x;
float offset = point.z / (HexMetrics.OuterRadius * 3f);
x -= offset;
y -= offset;
int iX = Mathf.RoundToInt(x);
int iY = Mathf.RoundToInt(y);
int iZ = Mathf.RoundToInt(-x - y);
if (iX + iY + iZ != 0)
{
float dX = Mathf.Abs(x-iX);
float dY = Mathf.Abs(y - iY);
float dZ = Mathf.Abs(-x-y-iZ);
if(dX>dY&&dX>dZ)
{
iX = -iY - iZ;
}
else if(dZ>dY)
{
iZ = -iX - iY;
}
}
return new HexCoordinates(iX,iZ);
}
}
Just convert from your HexCoordinates to Vector3 using any way:
create method for HexCoordinates, something like public Vector3 ToVector3() {...}
create implicit operator for implicit cast to Vector3
public struct HexCoordinates
{
public int X { get; private set; }
public int Z { get; private set; }
public int Y => -X - Z;
public HexCoordinates(int x,int z)
{
X = x;
Z = z;
}
...
public static implicit operator Vector3(HexCoordinates coords)
{
Vector3 result = // convert to Vector3
// Vector3 result = ToVector3() -- or like this for code reuse
return result;
}
public Vector3 ToVector3()
{
Vector3 result = //convert to Vector3
return result;
}
}
And then you can extend Unity's Object class and add overloading for Instantiate() method that will accept HexCoordinates, convert to Vector3 and call Instantiate()
public static class ObjectExtension
{
public static void Instantiate(this Object obj, Object origin, HexCoordinates coords, Quaternion q)
{
Vector3 position = coords.ToVector3();
obj.Instantiate(origin, position, q);
}
}
Also if you create implicit cast for your HexCoordinates to Vector3 you don't need to create overloading for Instantiate() method because converting will be implicitly
You are using using catlike coding's code. (This would have been helpful to mention in the question ;) ). In part 3 of the tutorial featuring this hex coordinate system, you can see how they would do something like below, accessing a hex inside of an array by calculating an index:
public HexCell GetCell (Vector3 position) {
position = transform.InverseTransformPoint(position);
HexCoordinates coordinates = HexCoordinates.FromPosition(position);
int index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
return cells[index];
}
So, since a HexCell has a transform.position, you can use that to get its center (making sure you don't access out of bounds):
private void TouchCell(Vector3 point)
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
int index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
if (index >=0 && index < cells.Length)
{
Vector3 worldPos = cells[index].transform.position;
Instantiate(cubes, worldPos, Quaternion.identity);
Debug.Log("Touched at:" + coordinates);
}
}
Better yet, it may be worthwhile to make a method to retrieve this index, for the sake of code reuse:
private bool IsValidCellIndex(Vector3 point, out int index)
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
return index >=0 && index < cells.Length;
}
private void TouchCell(Vector3 point)
{
if (IsValidCellIndex(point, out int index))
{
Vector3 worldPos = cells[index].transform.position;
Instantiate(cubes, worldPos, Quaternion.identity);
Debug.Log("Touched at:" + worldPos);
}
}
Or, just use GetCell as the tutorial does :)
I fight with that for 2 days and searching internet but i don't have any answer for my problem. In few words i want to have orbiting camera on screen and objects which I want also rotate independently from my camera. I implement Arcball rotation controller and I use it to rotate all object in the scene (maybe this cause problem). It looks ok when i have camera in starting point but when i start moving it and I want to rotate objects on the scene its looks like all object rotate like im in the camera starting point. Maybe here someone can help :D
My Arcball class:
public class ArcBallGL{
private bool isBeingDragged;
private Vector3 downPoint;
private Vector3 currentPoint;
public Vector3 BallCenter { get; set; }
private Quaternion down;
public Quaternion Now { get; set; }
private float width, height;
public ArcBallGL()
{
Reset();
width = height = 0.0f;
}
public void Reset()
{
isBeingDragged = false;
downPoint = Vector3.Zero;
currentPoint = Vector3.Zero;
down = Quaternion.Identity;
Now = Quaternion.Identity;
}
public void SetWindow(float width, float height)
{
this.width = width;
this.height = height;
}
public void OnBeginDrag(float x, float y)
{
isBeingDragged = true;
down = Now;
downPoint = ScreenToVector(x, y);
}
public void OnMove(float x, float y)
{
if (isBeingDragged == true)
{
currentPoint = ScreenToVector(x, y);
Now = down * QuatFromBallPoints(downPoint, currentPoint);
}
}
public void OnStopDrag()
{
isBeingDragged = false;
}
public Matrix4 GetRotationMatrix()
{
return Matrix4.CreateFromQuaternion(Now);
}
public Matrix4 GetRotationMatrixFormViewPort(Matrix4 camera)
{
var viewMatrix = Matrix4.CreateFromQuaternion(Now).Inverted();
viewMatrix.Normalize();
return viewMatrix * Matrix4.CreateFromQuaternion( camera.ExtractRotation()).Inverted();
}
private Vector3 ScreenToVector(float screentPointX, float screenPointY)
{
float x = (screentPointX - width * 0.5f) / (width * 0.5f);
float y = (screenPointY - height * 0.5f) / height;
float z = 0.0f;
float mag = x * x + y * y;
if (mag > 1.0f)
{
float scale = 1.0f / (float)Math.Sqrt(mag);
x *= scale;
y *= scale;
}
else
{
z = (float)Math.Sqrt(1.0f - mag);
}
return new Vector3(-x, y, -z);
}
private Quaternion QuatFromBallPoints(Vector3 from, Vector3 to)
{
float dot = Vector3.Dot(from, to);
Vector3 part = Vector3.Cross(from, to);
return new Quaternion(part.X, part.Y, part.Z, dot);
}
}
My Camera class:
public class Camera{
public Vector3 Position = Vector3.Zero;
private Vector3 Up = Vector3.UnitY;
public float MoveSpeed = 0.2f;
public float RotationSpeed = 0.01f;
public Quaternion RotQuar { get; set; }
public Camera(Vector3 startPos = new Vector3())
{
Position = startPos;
RotQuar = Quaternion.Identity;
}
public Matrix4 GetViewMatrix(Vector3 target)
{
//RotQuar.Conjugate();
var rotMatrix = Matrix4.CreateFromQuaternion(RotQuar);
var camPosition = Vector3.Transform(Position, rotMatrix);
var camDir = target - camPosition;
camDir.Normalize();
var camRight = Vector3.Cross(camDir, Vector3.UnitY);
camRight.Normalize();
var camUp = Vector3.Cross(camRight, camDir);
camUp.Normalize();
var cameraMat = Matrix4.LookAt(camPosition, target , camUp) ;
return cameraMat;
}
}
My mouse move methood
private void MouseMoved(object sender, MouseMoveEventArgs e)
{
if (e.Mouse.IsButtonDown(MouseButton.Left) && RIsDown)
{
foreach (Shape s in _shapes)
{
if (s.IsSelected)
{
Rotation.OnMove(e.Position.X, e.Position.Y);
s.RoatationQuat = Rotation.Now;
s.RotationMatrix = Rotation.GetRotationMatrixFormViewPort(_camera.GetViewMatrix(Vector3.Zero));
}
}
}
if (e.Mouse.IsButtonDown(MouseButton.Left) && CIsDown)
{
Rotation.OnMove(e.Position.X, e.Position.Y);
_camera.RotQuar = Rotation.Now;
}
}
I use index buffer to draw my objects on the scene. I think the problem lies in calculating the camera coords to objects coords but i don't know where i must calculate it. Thanks for help