- Home /
Rotate Object with mouse in RTS style game
Hello everyone I wanted to try to make a RTS style game, but I have stuck at the problem for many days. I want to rotate the building using a mouse, eg : when you are in build mode you can place object at the current mouse position, when the player press SHIFT key and move the mouse horizontal then object should rotate according to it. You many be thinking its easy just check if SHIFT key is pressed or not and if it is then rotate it. Yes, you are correct I tried to store the object position when SHIFT is press and rotate accordingly at that position but when I let go of the SHIFT key the mouse position has change as you have rotate the object and it snaps the object position to the new position. Do you guys have any solution to this problem, like after you release SHIFT key mouse position snaps to the object position or some other alternative?
I also tried to create a virtual mouse but then I have to write a whole lot of code for that virtual mouse to interact with the UI elements, so I have left it for now.
For reference you can see Planet Coaster. There you can press down Z key and move mouse horizontal and object rotates and when you let go of the Z key mouse position doesn't change.
Answer by andrew-lukasik · Oct 15, 2021 at 11:58 AM
Answer 1 "#LeaveCursorAlone"
using UnityEngine;
public class LetsSpawnAndRotate : MonoBehaviour
{
[SerializeField] GameObject _prefab = null, _prefabPreview = null;
GameObject _instance = null;
Vector3 _pos, _normal;
Vector2 _click;
void Update ()
{
var camera = Camera.main;
var ray = camera.ScreenPointToRay( Input.mousePosition );
if( Input.GetMouseButtonDown(0) )
{
if( Physics.Raycast(ray,out var hit) )
{
_pos = hit.point;
_normal = hit.normal;
_instance = GameObject.Instantiate(
_prefabPreview ,
_pos ,
Quaternion.AngleAxis( 90 , Quaternion.LookRotation(_normal)*Vector3.right ) * Quaternion.LookRotation(_normal)
);
_click = Input.mousePosition;
}
}
if( _instance!=null )
{
Vector2 mouseDelta = (Vector2) Input.mousePosition - _click;
if( Physics.Raycast(ray,out var hit) && (mouseDelta).sqrMagnitude>0 )
{
float yaw = -Mathf.Atan2( mouseDelta.y , mouseDelta.x );
Quaternion rotBase = Quaternion.AngleAxis( 90 , Quaternion.LookRotation(_normal)*Vector3.right ) * Quaternion.LookRotation(_normal);
Quaternion rotAux = Quaternion.AngleAxis( Mathf.Rad2Deg*yaw , _normal );
_instance.transform.rotation = rotAux * rotBase;
}
if( Input.GetMouseButtonUp(0) )
{
GameObject.Instantiate( _prefab , _instance.transform.position , _instance.transform.rotation );
Destroy( _instance );
_instance = null;
}
}
}
void OnDrawGizmos ()
{
Gizmos.DrawRay( _pos , _normal );
}
}
Answer 2 "Plane & simple"
using UnityEngine;
public class LetsRotateAndSpawn : MonoBehaviour
{
[SerializeField] GameObject _prefab = null, _prefabPreview = null;
[SerializeField] Vector3 _planeOrigin = Vector3.zero , _planeNormal = Vector3.up;
GameObject _instancePreview = null;
Vector3 _cursorPos = Vector3.zero, _cursorDragPosStart = Vector3.zero;
Quaternion _cursorRot = Quaternion.identity, _cursorDragRotStart = Quaternion.identity;
Camera _camera = null;
void Start ()
{
_camera = Camera.main;
_instancePreview = GameObject.Instantiate( _prefabPreview );
_instancePreview.SetActive( false );
}
void Update ()
{
Plane plane = new Plane( inNormal:_planeNormal , inPoint:_planeOrigin );
var ray = _camera.ScreenPointToRay( Input.mousePosition );
if( plane.Raycast( ray , out var hitDist ) )
{
Vector3 hitPoint = ray.origin + ray.direction * hitDist;
_cursorPos = hitPoint;
// store rot pivot info
if( Input.GetMouseButtonDown(1) )
{
_cursorDragPosStart = hitPoint;
_cursorDragRotStart = _cursorRot;
}
// update rotation
if( Input.GetMouseButton(1) )
{
Vector3 vec = _cursorPos - _cursorDragPosStart;
float screenPxDist = Vector2.Distance( _camera.WorldToScreenPoint(_cursorDragPosStart) , _camera.WorldToScreenPoint(_cursorPos) );
if( vec.sqrMagnitude>0 )
_cursorRot = Quaternion.Slerp(
_cursorDragRotStart ,
Quaternion.LookRotation( vec , _planeNormal ) ,
Mathf.SmoothStep( 0 , 1 , screenPxDist / ( Mathf.Min(Screen.width,Screen.height) * 0.05f ) )
);
}
// update preview mesh
if( !_instancePreview.activeSelf ) _instancePreview.SetActive( true );
_instancePreview.transform.SetPositionAndRotation( hitPoint , _cursorRot );
// spawn prefab
if( Input.GetMouseButtonDown(0) )
GameObject.Instantiate( _prefab , hitPoint , _cursorRot );
}
else _instancePreview.SetActive( false );
}
void OnDrawGizmos ()
{
if( _camera!=null && Input.GetMouseButton(1) )
{
Gizmos.color = Color.yellow;
Vector3 offset = new Vector3{ z=_camera.nearClipPlane*2f };
Gizmos.DrawLine(
_camera.ScreenToWorldPoint( Vector3.Scale( _camera.WorldToScreenPoint(_cursorDragPosStart) , new Vector3{x=1,y=1} ) + offset ) ,
_camera.ScreenToWorldPoint( Vector3.Scale( _camera.WorldToScreenPoint(_cursorPos) , new Vector3{x=1,y=1} ) + offset )
);
}
Gizmos.matrix = Matrix4x4.TRS( _planeOrigin , Quaternion.AngleAxis(0,_planeNormal) , Vector3.one );
Gizmos.DrawWireCube( Vector3.zero , new Vector3{ x=100 , z=100 } );
}
}
void Update()
{
if (buildMode)
{
if (Input.GetKeyDown(KeyCode.Z))
{
initialRotation += mouseDelta;
if (initialRotation < -180) initialRotation += 360;
else if (initialRotation > 180) initialRotation -= 360;
}
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
placeholderTile.transform.position = hit.point;
placeholderTile.transform.eulerAngles = Vector3.up * initialRotation;
}
}
}
mouseDelta is the mouse movement from last frame and placholderTile is the preview of the object.
Actually your solution works when you have decide the position and then you can set the rotation. I want to set position and rotation independent; eg: when your mouse is at position A then you can see the preview at that position, then you can change the rotation, then you can also check how it is at different position.
BTW thanks for your solution.
(sidenote) I would advise against using mouseDelta
(pixel count) as angle delta because this will make your rotation speed highly dependent on screen resolution. A mere detail but it will come back in QA.
Thanks for the feedback I will try to normalize it and set accordingly.
Also I forget to tell you in preview reply that you can also change the height at which you can build. I am using Plane.Raycast, you can change where plane is and then I place the object at that height.
That's why I also want to set rotation independent. So that player can preview the object at certain height, rotation before actually placing the object.
I just added answer #2 in my main post. It adds fine rotation control before spawning a prefab (drag right mouse button). There is also rotation "undo" built-in as a kind of gesture (if you move mouse cursor back to the same position).
Answer by Pangamini · Oct 14, 2021 at 10:43 PM
Check this answer on how to set the cursor position to a specific location. It's Windows only though, you'd have to find a specific soludion for different OS
Isn't there any unity API way to do that instead of doing manually for every OS. Like before there was a attribute called screen.lockcursor which lock the cursor at its current position. They made new class Cursor and in that there is a lockmode and when you set Cursor.lockmode to locked it set the mouse position to the center of the screen.
Well, no, that's what is also discussed in that answer I linked you. I guess you could find the solution for the couple of other OSes with mice that you are planning to support
Your answer
Follow this Question
Related Questions
How to find out which object is under a specific pixel 1 Answer
Raycast2d not working C# :( 1 Answer
How to make a 3D tank turret follow the DIRECTION of mouse cursor? 1 Answer
Script for camera movement using Arcball,Scripting Camera Movements with Arcball 0 Answers
How to create gameobjects by clicking in the scene view in the editor? 1 Answer