I have a tile engine and that's all working swell, my player walks around all good, I'm working on adding items, the player is always in the centre of the screen, until he gets close to the edges of the world then he starts going close to the edges.
When I draw items in the world, they draw fine, except when the player leaves the centre (at the edge of the world). I just can't wrap my head around how to fix this.
public static void Draw(SpriteBatch spriteBatch, World w, Item i, Player p, Point screenDimensions)
{
bool IsOnScreen = true;
float leftX = p.X - ((screenDimensions.X / 32) / 2);
float rightX = leftX + (screenDimensions.X / 32);
float topY = p.Y - ((screenDimensions.Y / 32) / 2);
float bottomY = topY + (screenDimensions.Y / 32);
if (i.x < leftX || i.x > rightX || i.y < topY || i.y > bottomY)
IsOnScreen = false;
if (IsOnScreen)
i.animation.Draw(spriteBatch, (int)Math.Floor((i.x - leftX) * 32), (int)Math.Floor((i.y - topY) * 32));
}
Its pretty self explainatory, the world is passed in to get the dimensions (w.worldDimensions.x for width, and .y for height), the item is used to get the i.x and i.y (location in game world, not on screen), the player for drawing it relative (contains .x and .y for location) and then the screenDimensions.
Well it does not look very clear to me. Are you using a camera class? If you use a camera class and use that to navigate your world this should never happen.
Here is a basic one i currently use for my project.
class Camera
{
float zoom;
public float Rotation { get; private set; }
public Vector2 Position { get; private set; }
Matrix transform;
int velocity = 60;
UserInput input;
public float Zoom
{
get { return zoom; }
set { zoom = value; if (zoom < 0.1f) zoom = 0.1f; } // Negative zoom will flip image
}
public Camera(UserInput input)
{
zoom = 1.0f;
Rotation = 0f;
Position = new Vector2(0, 0);
this.input = input;
}
public void MoveCam()
{
if (input.IsKeyHold(Keys.Up))
{
Position += new Vector2(0, -velocity);
}
if (input.IsKeyHold(Keys.Left))
{
Position += new Vector2(-velocity, 0);
}
if (input.IsKeyHold(Keys.Down))
{
Position += new Vector2(0, velocity);
}
if (input.IsKeyHold(Keys.Right))
{
Position += new Vector2(velocity, 0);
}
if (input.IsKeyHold(Keys.OemMinus))
{
Zoom -= .01f * Zoom;
}
else if (input.IsKeyHold(Keys.OemPlus))
{
Zoom += .01f * Zoom;
}
}
public void FollowCam(int xPos, int yPos)
{
Position = new Vector2(xPos * TileData.Width, yPos * TileData.Height);
}
public Matrix TransformMatrix(GraphicsDevice device)
{
transform = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
Matrix.CreateRotationX(MathHelper.ToRadians(Rotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(Rotation)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)) *
Matrix.CreateScale(new Vector3(zoom, zoom, 0)) *
Matrix.CreateTranslation(new Vector3(device.Viewport.Width * 0.5f, device.Viewport.Height * 0.5f, 0));
return transform;
}
}
Just instantiate the class like in main and use this in your draw method.
batch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, camera.TransformMatrix(graphicsDevice));
batch.End()
Draw everything in your world within this spritebatch and use a new basic to draw to screen cooridinates, like a gui/hud. You can use the camera move method to move it manually and the lock to lock it on any location (it follows if updated).
If you have large maps you might want to render only necessary tiles. I do it like this in my map class:
public void Draw(SpriteBatch batch, Vector2 camPosition, float camZoom, GraphicsDevice device)
{
float top = (camPosition.Y / TileData.Height) - ((device.Viewport.Height / 2) / TileData.Height + 1) / camZoom;
float bottom = (camPosition.Y / TileData.Height) + ((device.Viewport.Height / 2) / TileData.Height + 2) / camZoom;
float left = (camPosition.X / TileData.Width) - ((device.Viewport.Width / 2) / TileData.Width + 1) / camZoom;
float right = (camPosition.X / TileData.Width) + ((device.Viewport.Width / 2) / TileData.Width + 2) / camZoom;
for (int y = (int)top; y < (int)bottom; y++)
{
for (int x = (int)left; x < (int)right; x++)
{
if (y >= 0 && y < map.GetLength(1) && x >= 0 && x < map.GetLength(0))
{
batch.Draw(map[x, y].texture, new Rectangle(x * TileData.Width, y * TileData.Height, TileData.Width, TileData.Height), Color.White);
}
}
}
}
Here first i figure out which tiles to draw from each direction. Note the camZoom, you want more tiles to be drawn when zooming out. Then i use these "bounderies" in my for loops, the if statement makes sure i am not accessing tiles that dont exist (out of bounds).
Related
I want to move my boat to the position where i clicked BUT with a realistic movement and rotation :
(http://i.imgur.com/Pk8DOYP.gif)
Here is my code (attached to my boat gameobject) :
Basically, when i click somewhere, it move the boat until it reach the point that i clicked in the first place (i simplified my code for you)
using UnityEngine;
using System.Collections;
public class BoatMovement : MonoBehaviour {
private Vector3 targetPosition;
private float speed = 10f;
private bool isMoving;
void Update(){
if (!isMoving && Input.GetMouseButton (0)) {
targetPosition = Camera.main.ScreenToWorldPoint (Input.mousePosition);
isMoving = true;
}
if (isMoving) {
moveToPosition ();
}
}
void moveToPosition() {
transform.position = Vector3.MoveTowards (transform.position, new Vector3(targetPosition.x, targetPosition.y, 0f), speed * Time.deltaTime);
if (transform.position.x == targetPosition.x && transform.position.y == targetPosition.y) {
isMoving = false;
}
}
}
After some research and tries, i didn't find a way to do what i want.
Thanks for your help
There's two parts to this problem, which should be kept completely separate from each other and at no time does the equation of one interfere with the equation of the other.
The first is the forward movement of the ship, which can be achieved in two ways:
If you are using a rigidbody
void Propel()
{
float speed = 150f;
RigidBody rb = GetComponent<RigidBody>();
Transform transform = GetComponent<Transform>();
rb.AddForce(transform.forward * speed * Time.deltaTime); // <--- I always forget if its better to use transform.forward or Vector3.forward. Try both
}
If no rigidbody
void Propel()
{
float speed = 150f;
Transform transform = GetComponent<Transform>();
transform.Translate(transform.forward * speed * Time.deltaTime, Space.World);
}
Now the second would be the turning of the ship, which also can be achieved in two ways:
With rigidbody
IEnumerator TurnShip(Vector3 endAngle)
{
float threshold = Single.Epsilon;
float turnSpeed = 150f;
RigidBody rb = GetComponent<RigidBody>();
while (Vecotr3.Angle(transform.forward, endAngle) > threshold)
{
rb.AddTorque(transform.up * turnSpeed * Time.deltaTime);
yield return null;
}
}
Without rigidbody
IEnumerator TurnShip(Vector3 endAngle)
{
float threshold = Single.Epsilon;
float turnSpeed = 150f;
float step = turnSpeed * Time.deltaTime;
while (Vector3.Angle(transform.forward, endAngle) > threshold)
{
newDir = Vector3.RotateTowards(transform.forward, endAngle, step);
transform.rotation = Quaternion.LookRotation(newDir);
yield return null;
}
}
Then of course, the IEnumerators are called like so:
StartCoroutine(TurnShip(new Vector3(12f, 1f, 23f));
Couple things to note:
This is pseudo code, i haven't tested any of it so just know that making it work is up to you, i'm only providing you with the correct path to follow.
All the variables in the beginnings of the methods are meant to be global variables, so declare them where you please.
I've used a Transform called target here, just replace it with the Vector from your mouse click.
public Transform target;
private void Update()
{
transform.position += transform.forward * Time.deltaTime;
Vector3 targetDir = target.position - transform.position;
float step = Time.deltaTime / Mathf.PI;
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0f);
transform.rotation = Quaternion.LookRotation(newDir);
}
The key to this is the RotateTowards function and calculating a new vector to look towards on each frame based on the current position.
If your boats have some kind of rotation speed stat then modify the float called step to suit. If they have a speed stat then modify the transform.position += transform.forward * Time.deltaTime with some multiplier.
I guess you'll want to put some conditions in as to when to stop the movement and rotation, perhaps using a coroutine.
Declare
private Vector3 targetPosition;
private float targetDistance;
Then at the start of your move, do :
targetPosition = Camera.main.ScreenToWorldPoint (Input.mousePosition);
targetDistance = Vector3.Distance(targetPosition, transform.position);
While moving call this : (in your update loop for example)
turnSpeed = 0.062f * targetDistance;
moveSpeed = 35f * targetDistance;
//Important /!\ : you need to add Linear drag on your rigidbody or it will keep adding
RigidBody.AddForce(transform.up * moveSpeed * Time.deltaTime);
var newRotation = Quaternion.LookRotation (transform.position - targetPosition, Vector3.forward);
newRotation.x = 0f;
newRotation.y = 0f;
transform.rotation = Quaternion.Slerp (transform.rotation, newRotation, Time.deltaTime * turnSpeed);
Finally, add a linear drag value to your rigidbody (1 would be ok).
Thanks to maksymiuk who helped me and took a lot of his time trying to figure out a solution.
I'm having a problem with the rotation of a game object.
I'm able to rotate the object in the direction/angle of another game object.
But after the first rotation the game object i'm rotating based on the delta rotation of the source game object It starts rotating in a different direction because it has been rotated once already I guess.
So is there a way so that after the first rotation I can reset the rotation so It moves again in the angle/direction the other object is rotating?
This is my current code below, i'm not a math expert so I might be doing things wrong ..
So the basic thing I want is that the second game object rotates in the same direction the base game object rotates but I don't want the rotation values to be equal, the rotation values doesn't have to be the same, it only has to rotate in the same angle/direction as the base game object rotates.
TiltAgainstObject is the source game object.
The script is attached to the second game object.
Any help is welcome :).
public class Tilt : MonoBehaviour {
public GameObject TiltAgainstObject;
private float rotX;
private float rotY;
private float rotZ;
private float rotW;
private float deltaX;
private float deltaY;
private float deltaZ;
private float deltaW;
// Use this for initialization
void Start () {
rotX = TiltAgainstObject.transform.rotation.x;
rotY = TiltAgainstObject.transform.rotation.y;
rotZ = TiltAgainstObject.transform.rotation.z;
rotW = TiltAgainstObject.transform.rotation.w;
}
// Update is called once per frame
void Update () {
if(OVRInput.Get(OVRInput.Button.One))
{
deltaX = TiltAgainstObject.transform.rotation.x - rotX;
deltaY = TiltAgainstObject.transform.rotation.y - rotY;
deltaZ = TiltAgainstObject.transform.rotation.z - rotZ;
deltaW = TiltAgainstObject.transform.rotation.w - rotW;
gameObject.transform.localRotation = new Quaternion(gameObject.transform.rotation.x + deltaX,
gameObject.transform.rotation.y + deltaY,
gameObject.transform.rotation.z + deltaZ,
gameObject.transform.rotation.w + deltaW);
}
rotX = TiltAgainstObject.transform.rotation.x;
rotY = TiltAgainstObject.transform.rotation.y;
rotZ = TiltAgainstObject.transform.rotation.z;
rotW = TiltAgainstObject.transform.rotation.w;
}
}
The question seems a bit confusing, bur if you want the second gameobject (where your script is) to rotate with the same direction of the first object you should do something like this:
public class Tilt : MonoBehaviour {
public GameObject TiltAgainstObject;
private float rotX;
private float rotY;
private float rotZ;
private float rotW;
private float deltaX;
private float deltaY;
private float deltaZ;
private float deltaW;
// Use this for initialization
void Start () {
rotX = TiltAgainstObject.transform.rotation.x;
rotY = TiltAgainstObject.transform.rotation.y;
rotZ = TiltAgainstObject.transform.rotation.z;
rotW = TiltAgainstObject.transform.rotation.w;
deltaX = 0;
deltaY = 0;
deltaZ = 0;
deltaW = 0;
}
// Update is called once per frame
void Update () {
rotX = TiltAgainstObject.transform.rotation.x;
rotY = TiltAgainstObject.transform.rotation.y;
rotZ = TiltAgainstObject.transform.rotation.z;
rotW = TiltAgainstObject.transform.rotation.w;
this.gameObject.transform.localRotation = new Quaternion(rotX + deltaX,
rotY + deltaY,
rotZ + deltaZ,
rotW + deltaW);
}
This way the second object will rotate in the same direction of the first object, if you want to change the speed of rotation just modify the deltas.
If you want the rotation to occur only when you press the button.One just pust it inside the if statement instead:
if(OVRInput.Get(OVRInput.Button.One))
{
this.gameObject.transform.localRotation = new Quaternion(rotX + deltaX,
rotY + deltaY,
rotZ + deltaZ,
rotW + deltaW);
}
I think the problem is that you're assigning the world coordinate rotation to your local coordinate localRotation, a typo maybe? Besides that though, I'm not sure why you're editing the components of the Quaternion directly, generally it's better to just add them together and use Unity's functions since dealing with 4 dimensions is often error-prone. You might try something like this:
public class Tilt : MonoBehaviour {
public GameObject TiltAgainstObject;
private Quaternion prevRotation;
private Quaternion currentRotation;
void Start()
{
prevRotation = TiltAgainstObject.transform.rotation;
}
void Update()
{
currentRotation = TiltAgainstObject.transform.rotation;
if (OVRInput.Get(OVRInput.Button.One))
gameObject.transform.rotation *= Quaternion.RotateTowards(prevRotation, currentRotation, 1000f);
prevRotation = currentRotation;
}
I´m newbie in Unity. I want rotate my 2D object based on user touch moved (moved finger on the screen). I have this code:
void Update ()
{
if (Input.touches.Length > 0) {
t = Input.GetTouch (0);
if (t.phase == TouchPhase.Moved) {
Vector3 movePos = new Vector3 (t.position.x, t.position.y, 0);
var objectPos = Camera.main.WorldToScreenPoint (transform.position);
var dir = movePos - objectPos;
transform.rotation = Quaternion.Euler (new Vector3 (0f, 0f, Mathf.Atan2 (dir.y, dir.x) * Mathf.Rad2Deg));
}
}
}
This code rotate the object based on user touch but when I touch screen again in another position and do touch move, it will rotate the whole object to the actual touch and then it will do correct object rotation based on touch move.
And I dont´t want rotate the whole object based on touch position but rotate the object only based on touch move. Do you understand me? Can you help me? How should I rewrite my code?
If I understand you, try to use this code below:
private float turnSpeed = 5f;
private Vector2 movement;
void Update()
{
Vector2 currentPosition = transform.position;
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Moved)
{
Vector2 moveTowards = Camera.main.ScreenToWorldPoint(touch.position);
movement = moveTowards - currentPosition;
movement.Normalize();
}
}
float targetAngle = Mathf.Atan2(movement.y, movement.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(0, 0, targetAngle), turnSpeed * Time.deltaTime);
}
Let me know if is what you want. Also, there is a complete sample here: https://github.com/joaokucera/unity-2d-object-rotation
Look into using deltaPosition instead of position on your touch. That should get you in the right direction.
var movedVector = t.deltaPosition;
Edit:
Here is a possible integration with your existing code. I don't have Unity on this PC so this is entirely untested. The main idea is you are getting a change in the finger position between frames. You then scale that change by move speed, and of course, the change in time between frame renders (delta time).
How the object rotates relative to that information is up to you. I just inserted the logic into your existing code.
float moveSpeed = 2.0f;
void Update ()
{
if (Input.touches.Length > 0) {
t = Input.GetTouch (0);
if (t.phase == TouchPhase.Moved) {
var delta = t.deltaPosition * moveSpeed * Time.deltaTime;
transform.rotation = Quaternion.Euler (new Vector3 (0f, 0f, Mathf.Atan2 (delta .y, delta.x) * Mathf.Rad2Deg));
}
}
}
We are trying to implement zoom buttons on top of a map created in D3 - essentially as it works on Google maps. The zoom event can be dispatched programmatically using
d3ZoomBehavior.scale(myNewScale);
d3ZoomBehavior.event(myContainer);
and the map will zoom using the current translation for the view. If using zoom buttons the focal point (zoom center) is no longer the translation but the center of the view port. For zoom using the scroll wheel we have the option of using zoom.center - but this apparently have no effect when dispatching your own event.
I'm confused as to how a calculate the next translation taking the new scaling factor and the view port center into account.
Given that I know the current scale, the next scale, the current translation and the dimensions of the map view port how do I calculate the next translation, so that the center of the view port do not change?
I've recently had to do the same thing, and I've got a working example up here http://bl.ocks.org/linssen/7352810. Essentially it uses a tween to smoothly zoom to the desired target scale as well as translating across by calculating the required difference after zooming to centre.
I've included the gist of it below, but it's probably worth looking at the working example to get the full effect.
html
<button id="zoom_in">+</button>
<button id="zoom_out">-</button>
js
var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
function zoomed() {
svg.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function interpolateZoom (translate, scale) {
var self = this;
return d3.transition().duration(350).tween("zoom", function () {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function (t) {
zoom
.scale(iScale(t))
.translate(iTranslate(t));
zoomed();
};
});
}
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.2,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
d3.selectAll('button').on('click', zoomClick);
A more succinct version of Wil's solution:
var vis = d3.select('.vis');
var zoom = d3.behavior.zoom()...
var width = .., height = ..;
function zoomByFactor(factor) {
var scale = zoom.scale();
var extent = zoom.scaleExtent();
var newScale = scale * factor;
if (extent[0] <= newScale && newScale <= extent[1]) {
var t = zoom.translate();
var c = [width / 2, height / 2];
zoom
.scale(newScale)
.translate(
[c[0] + (t[0] - c[0]) / scale * newScale,
c[1] + (t[1] - c[1]) / scale * newScale])
.event(vis.transition().duration(350));
}
};
function zoomIn() { zoomByFactor(1.2); }
function zoomOut() { zoomByFactor(0.8); }
I've found this to be quite difficult to do in practice. The approach I've taken here is to simply create a mouse event that triggers the zoom when the zoom buttons are used. This event is created at the center of the map.
Here's the relevant code:
.on("click", function() {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent(
'dblclick', // in DOMString typeArg,
true, // in boolean canBubbleArg,
true, // in boolean cancelableArg,
window,// in views::AbstractView viewArg,
120, // in long detailArg,
width/2, // in long screenXArg,
height/2, // in long screenYArg,
width/2, // in long clientXArg,
height/2, // in long clientYArg,
0, // in boolean ctrlKeyArg,
0, // in boolean altKeyArg,
(by > 0 ? 0 : 1), // in boolean shiftKeyArg,
0, // in boolean metaKeyArg,
0, // in unsigned short buttonArg,
null // in EventTarget relatedTargetArg
);
this.dispatchEvent(evt);
});
The whole thing is a bit of a hack, but it works in practice and I've found this much easier than to calculate the correct center for every offset/zoom.
I am trying to center an image in the middle of the stage and scale it proportionally to the correct aspect ratio based on its loader's size when the image is loading.
In my main app runner I do:
private var rec:Rectangle = new Rectangle();
private var img:BitmapDisplay = new BitmapDisplay();
stage.addEventListener(Event.RESIZE, setRect);
img.imageURL = "some/path/to/image.jpg";
addChild(img);
private function setRect():void
{
var horz:Number = stage.stageWidth / 16;
var vert:Number = stage.y + stage.stageHeight / 16;
rec.x = horz;
rec.y = 80;
rec.width = stage.stageWidth - horz;
rec.height = stage.stageHeight - (vert * 2) - rec.y;
img.boundary = rec;
}
Then in my image (BitmapDisplay) class I have:
// sets the boundary of the image and resizes it
public function set boundary(rect:Rectangle):void
{
_imageBoundary = rect;
resizeImage();
}
private function resizeImage():void
{
var aspect:Number = _ldr.content.width / _ldr.content.height;
var cAspect:Number = _imageBoundary.width / _imageBoundary.height;
if (aspect <= cAspect)
{
_ldr.height = _imageBoundary.height;
_ldr.width = aspect * _ldr.height;
}
else
{
_ldr.width = _imageBoundary.width;
_ldr.height = _ldr.width / aspect;
}
var _pad:int = 7;
_ldr.x = (_imageBoundary.width - _ldr.width) / 2 + _imageBoundary.x - _pad;
_ldr.y = (_imageBoundary.height - _ldr.height) / 2 + _imageBoundary.y - _pad;
}
}
Is there something obvious keeping this from working right?
I want a 16th of the stage to be the bounds for the image and for the image to scale/resize based on this rect.
Thanks...
It turns out that this had more to do with the loader not being ready to be resized. There was some error checking in my code that needed to be reformatted, so now when the resize function gets called, it doesn't do anything until the loader is ready and the boundary is set.