I'm trying to create a simple test for Box2d, Cocos2d, and Android. All I need is to put one body on the screen, and have it respond to gravity. I looked everywhere and there are good tutorials for non-Android applications but none of them has gravity on Android working. Can anyone help?
This is the code I'm using. I took it, and modified lightly from here: http://www.expobrain.net/2012/05/12/box2d-physics-simulation-on-android/
For creating the world:
World world = new World(new Vec2(2.0f, 8.0f), false);
And this is how I create the body:
public void setupBallBody() {
CGSize size = CCDirector.sharedDirector().winSize();
CGPoint pos = CGPoint.make(size.width / 1.2f, size.height / 1.2f);
// Create Dynamic Body
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyType.DYNAMIC;
bodyDef.position.set(screenToWorld(pos));
ballBody = world.createBody(bodyDef);
MassData md = new MassData();
md.mass = 5;
ballBody.setMassData(md);
// Create Shape
CircleShape ballShape = new CircleShape();
ballShape.m_radius = SMILE_RADIUS;
// Create fixture
FixtureDef ballFixture = new FixtureDef();
ballFixture.shape = ballShape;
ballFixture.density = SMILE_DENSITY;
ballFixture.friction = SMILE_FRICTION;
ballFixture.restitution = SMILE_RESTITUTION;
// Assign fixture to Body
ballBody.createFixture(ballFixture);
// Set sprite
final CCSprite ballSprite = CCSprite.sprite("ball.jpg");
ballSprite.setPosition(pos);
addChild(ballSprite, 0);
ballBody.setUserData(ballSprite);
}
This is my "tick" method (which I'm not sure is part of what makes gravity working but I include it here for completeness.)
public void tick(float dt) {
synchronized (world) {
world.step(1/60, 10, 10);
}
// Update sprites
for (Body b = world.getBodyList(); b != null; b = b.getNext()) {
if(b == ballBody) {
CCSprite ballSprite = (CCSprite)ballBody.getUserData();
if(ballSprite != null) {
ballSprite.setPosition(worldToScreen(ballBody.getPosition())));
ballSprite.setRotation(-1.0f * (float)Math.toDegrees((ballBody.getAngle())));
}
}
}
}
My guessing here is this line may be the problem.-
world.step(1/60, 10, 10);
step function handles the bodies positions based on the time passed since last step. You're doing an integer division 1/60, which result is 0. Try 1.0f / 60.0f instead.
Otherwise, you're telling your world that 0 milliseconds passed since the last step, so bodies will always remain at their initial position.
However, it's not good practice to 'hardcode' your time step. You better pass to your world step the delta time your receiving.-
world.step(dt, 10, 10);
Also, you may simplify your loop to work for any body with an attached CCSprite, like this.-
for (Body b = world.getBodyList(); b != null; b = b.getNext()) {
CCSprite sprite = (CCSprite)b.getUserData();
if(sprite != null) {
sprite.setPosition(worldToScreen(b.getPosition())));
sprite.setRotation(-1.0f * (float)Math.toDegrees((b.getAngle())));
}
}
Related
so as my previous threads show, I am creating a gameObject from sprites images at runtime using this code:
tex = Resources.Load<Texture2D>("pig") as Texture2D;
Sprite sprite = new Sprite();
sprite = Sprite.Create(tex, new Rect(0, 0, 250, 150), new Vector2(0.5f, 0.5f));
GameObject newSprite = new GameObject();
newSprite.AddComponent<Rigidbody2D>();
newSprite.GetComponent<Rigidbody2D>().gravityScale = 0f;
newSprite.AddComponent<ObjectMovement>();
newSprite.AddComponent<SpriteRenderer>();
SR = newSprite.GetComponent<SpriteRenderer>();
SR.sprite = sprite;
As you see I added a script "ObjectMovement", I want to check in this script if someone is dragging this particular gameObject and if so, make it follow the touch position, just to mention - this game is 2D. I never used RaysorRaycast so I am not sure where I gone wrong. Anyway here is my script code:
public SpriteRenderer selection=null;
void Update()
{
if (Input.touchCount >= 1)
{
foreach (Touch touch in Input.touches)
{
Ray ray = Camera.main.ScreenPointToRay(touch.position);
RaycastHit hit;
switch (touch.phase)
{
case TouchPhase.Began:
if (Physics.Raycast(ray, out hit, 100))
selection = hit.transform.gameObject.GetComponent<SpriteRenderer>();
break;
case TouchPhase.Moved:
selection.transform.position = new Vector2(selection.transform.position.x + touch.position.x / 10, selection.transform.position.y + touch.position.y / 10);
break;
case TouchPhase.Ended:
selection = null;
break;
}
}
}
}
So basically - when touching the screen, fire a ray and check which gameObject is in this position, when moving the finger make it follow it. Drag and drop. Thanks.
EDIT: I noticed the script is attached to every gameObject which is not effective, any ideas?
For 2D, you use RaycastHit2D and Physics2D.Raycast instead of RaycastHit and Physics.Raycast. Those are for 3D. Secondly, Make sure to attach a collider to the Sprite. Since this is a 2D game, the collider must have word "2D" in it. For example, Box Colider 2D from the Editor. You can also use Circle Collider 2D.
I noticed the script is attached to every gameObject which is not
effective, any ideas?
Just create an empty GameObject and attach that script to it. That's it.
Here is fixed version of your code:
float tempZAxis;
public SpriteRenderer selection;
void Update()
{
Touch[] touch = Input.touches;
for (int i = 0; i < touch.Length; i++)
{
Vector2 ray = Camera.main.ScreenToWorldPoint(Input.GetTouch(i).position);
RaycastHit2D hit = Physics2D.Raycast(ray, Vector2.zero);
switch (touch[i].phase)
{
case TouchPhase.Began:
if (hit)
{
selection = hit.transform.gameObject.GetComponent<SpriteRenderer>();
if (selection != null)
{
tempZAxis = selection.transform.position.z;
}
}
break;
case TouchPhase.Moved:
Vector3 tempVec = Camera.main.ScreenToWorldPoint(touch[i].position);
tempVec.z = tempZAxis; //Make sure that the z zxis never change
if (selection != null)
{
selection.transform.position = tempVec;
}
break;
case TouchPhase.Ended:
selection = null;
break;
}
}
}
That would only work on mobile but not on the Desktop Build. I suggest you implement IBeginDragHandler, IDragHandler, IEndDragHandler and override the functions that comes with them. Now, it will work with both mobile and desktop platforms.
Note: For the second solution you have to attach the script below to all Sprites you want to drag unlike the first script above.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Dragger : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
Camera mainCamera;
float zAxis = 0;
Vector3 clickOffset = Vector3.zero;
// Use this for initialization
void Start()
{
//Comment this Section if EventSystem system is already in the Scene
addEventSystem();
mainCamera = Camera.main;
mainCamera.gameObject.AddComponent<Physics2DRaycaster>();
zAxis = transform.position.z;
}
public void OnBeginDrag(PointerEventData eventData)
{
clickOffset = transform.position - mainCamera.ScreenToWorldPoint(new Vector3(eventData.position.x, eventData.position.y, zAxis));
}
public void OnDrag(PointerEventData eventData)
{
//Use Offset To Prevent Sprite from Jumping to where the finger is
Vector3 tempVec = mainCamera.ScreenToWorldPoint(eventData.position) + clickOffset;
tempVec.z = zAxis; //Make sure that the z zxis never change
transform.position = tempVec;
}
public void OnEndDrag(PointerEventData eventData)
{
}
//Add Event Syste to the Camera
void addEventSystem()
{
GameObject eventSystem = new GameObject("EventSystem");
eventSystem.AddComponent<EventSystem>();
eventSystem.AddComponent<StandaloneInputModule>();
}
}
I am using Androidplot to display real time data. The real time data is dynamic and the x axis is time. The domain is a fixed timespan. Because the values could be any double, rangeStepMode is set to subdivide the y-axis. The issue is that when all the values are the same, the plot does not draw correctly.
You can see in the image that there are no range grids or labels.
From this related question:
This happens because Androidplot does not have enough information to automatically calculate what a reasonable domain/range scale would be from a single point.
This makes sense, and the linked solution of setting a non-zero range boundary works but instead of an arbitrary range like miny - 1to maxy + 1, I'd like for there to be only one labeled range line in these cases across the middle of the screen.
My Activity:
public class SimpleXYPlotActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_xy_plot_example);
XYPlot plot = (XYPlot) findViewById(R.id.plot);
Number[] series1Numbers = new Number[]{
1,
1,
};
XYSeries series1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1", series1Numbers);
LineAndPointFormatter series1Format = new LineAndPointFormatter(Color.RED, Color.GREEN, null, null);
plot.addSeries(series1, series1Format);
StepMode stepMode = StepMode.SUBDIVIDE;
int stepValue = 10;
plot.setRangeStepMode(stepMode);
plot.setRangeStepValue(stepValue);
}
}
My Layout:
<com.androidplot.xy.XYPlot
android:id="#+id/plot"
style="#style/APDefacto.Dark"
android:layout_width="match_parent"
android:layout_height="match_parent"
ap:rangeStep="5"
ap:rangeStepMode="subdivide"
ap:domainStep="1"
ap:domainStepMode="increment_by_val"
/>
What I've tried
The following works on the simple xy example, but when I try setting it within minmax in my time series class, the thread drawing the domain gridlines gets into a very long loop as it calculates xPix to be very very large. This also isn't a very solution for situations where I have multiple series. Any ideas on how I can get the results I want?
Double minY = null;
Double maxY = null;
for (Double aDouble : series1Numbers) {
if (minY == null) {
minY = aDouble;
} else {
minY = Math.min(minY, aDouble);
}
if (maxY == null) {
maxY = aDouble;
} else {
maxY = Math.max(maxY, aDouble);
}
}
if (minY.equals(maxY)) {
plot.setUserRangeOrigin(minY);
plot.setRangeBoundaries(0, 2 * maxY, BoundaryMode.FIXED);
plot.setRangeStepMode(StepMode.INCREMENT_BY_PIXELS);
plot.setRangeStepValue(Double.POSITIVE_INFINITY);
} else {
plot.setUserRangeOrigin(0);
plot.setRangeBoundaries(minY, maxY, BoundaryMode.FIXED);
plot.setRangeStepMode(StepMode.SUBDIVIDE);
plot.setRangeStepValue(5);
}
This wont get rid of the CPU overhead but you can use PlotUtils.minMax to find these values for you
If implementing and using FastXYSeries is a possibility for you then the overhead of using minMax drops away too, as the calculation only ever gets done when your XYSeries data changes.
I'm trying to draw annotation and in 80% of the time the annotation draw properly but sometimes the annotation is cut in the middle and after i do zoom in and do zoom out the annotation is drawing completely.
this is the code where i draw the annotation:
public void createNoteIconOnPage(AnnotationData annotationData, Point noteIconPoint) {
try {
mPDFView.docLock(true);
PDFDoc pdfDoc = mPDFView.getDoc();
double[] pts = mPDFView.convScreenPtToPagePt(noteIconPoint.x, noteIconPoint.y, annotationData.getPage());
Point p = new Point(pts[0], pts[1]);
com.pdftron.pdf.annots.Text text = com.pdftron.pdf.annots.Text.create(pdfDoc, p);
text.setUniqueID(annotationData.getUniqueId());
//creating the annotation appearance - icon
// Let's create an appearance for the annotation using an image
ElementBuilder builder = new ElementBuilder();
ElementWriter writer = new ElementWriter();
writer.begin(pdfDoc);
Bitmap imageBitmap = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
imageBitmap.eraseColor(Color.RED);
Image image = Image.create(mPDFView.getDoc(), imageBitmap);
// Image image = Image.create(pdfDoc, annotationData.getDrawable());
int w = image.getImageWidth(), h = image.getImageHeight();
Element element = builder.createImage(image, 0, 0, w, h);
writer.writePlacedElement(element);
writer.writeElement(builder.createTextBegin(Font.create(pdfDoc, Font.e_times_roman), 12));
writer.writeElement(element);
writer.writeElement(builder.createTextEnd());
Obj appearance = writer.end();
appearance.putRect("BBox", 0.1, 0.1, w, h);
text.setAppearance(appearance);
/*
The left icons spouse to be bigger the the regular icons
*/
if(annotationData.getType() == AnnotationData.AnnotationType.LINK && (annotationData.getShard() == AnnotationData.LEFT_LINK_A || annotationData.getShard() == AnnotationData.LEFT_LINK_B)){
text.setRect(new Rect(pts[0],pts[1],pts[0] + 30,pts[1] + 30));
}
if (annotationData.getType() == AnnotationData.AnnotationType.NOTE) {
text.setContents(AnnotationData.NOTE_TYPE_CONTENTS);
} else if (annotationData.getType() == AnnotationData.AnnotationType.LINK) {
text.setContents(AnnotationData.LINK_TYPE_CONTENTS);
}
Page page = pdfDoc.getPage(annotationData.getPage());
if (page != null) {
page.annotPushBack(text);
}
mAnnotPushedBack = true;
mAnnot = text;
mAnnotPageNum = mDownPageNum;
buildAnnotBBox();
mPDFView.update(mAnnot, mAnnotPageNum);
raiseAnnotationAddedEvent(mAnnot, mAnnotPageNum);
} catch (Exception ex) {
Log.e(PDFTronReader.TAG, ex.toString());
mNextToolMode = ToolManager.e_pan;
} finally {
mPDFView.docUnlock();
}
mPDFView.waitForRendering();
}
and this is how it's look:
as you can see, we have 3 annotations in the left size (3 red squares) and the first one is cut in the middle
does somebody knows why it's happen?
thanks you all.
I think there is a discrepancy between what annotationData.getPage() returns and what mAnnotPageNum is.
I would consolidate this code around one of those two variables. That is, use one or the other. I suspect 20% of the time they are not the same number.
Furthermore, Text annots only have a position, as their size is fixed. They don't scale the same as normal annotations. So I would comment out the call to buildAnnotBBox.
Other things I would change.
As mentioned above, the size is fixed. So remove the following line
text.setRect(new Rect(pts[0],pts[1],pts[0] + 30,pts[1] + 30));
You set the BBox origin at 0.1, when this should be 0 based on your custom appearance.
Is there a way to get a picture of the player and then cropping the face for a sprite or something?
Like the apps that add your face to a body, but the face would be stored in some way.
Following this question answer, you can retrieve the picture from the Android Camera and save it as a texture. Then you use a Texture2D containing your mask (let's say a circle on the middle with alpha around it) to check which byte you want to save and which byte will be transparent. Don't forget to set this Texture2D import properties to Advanced and check Read/Write enabled. Finally you can load the newly created picture as a sprite using either Unity WWW class or System.IO.ReadAllBytes.
Here is a quick implementation of this:
[SerializeField]
private Texture2D maskTexture;
private WebCamTexture webCamTexture;
protected void Start()
{
GetComponent<RawImage>().rectTransform.sizeDelta = new Vector2(maskTexture.width, maskTexture.height);
webCamTexture = new WebCamTexture();
GetComponent<Renderer>().material.mainTexture = webCamTexture;
webCamTexture.Play();
}
public void SavePicture()
{
StartCoroutine(SavePictureCoroutine());
}
private IEnumerator SavePictureCoroutine()
{
yield return new WaitForEndOfFrame();
RawImage rawImageToRead = GetComponent<RawImage>();
//This is to save the current RenderTexture: RenderTexture.GetTemporary() and RenderTexture.ReleaseTemporary() can also be used
RenderTexture previousRenderTexture = RenderTexture.active;
RenderTexture.active = rawImageToRead.texture as RenderTexture;
Texture2D renderedTexture = new Texture2D(maskTexture.width, maskTexture.height, TextureFormat.ARGB32, false);
renderedTexture.ReadPixels(new Rect(Screen.width * 0.5f - maskTexture.width * 0.5f, Screen.height * 0.5f - maskTexture.height * 0.5f, renderedTexture.width, renderedTexture.height), 0, 0);
renderedTexture.Apply();
RenderTexture.active = previousRenderTexture;
//----
for(int i = 0; i < renderedTexture.width; i++)
{
for(int j = 0; j < renderedTexture.height; j++)
{
if(maskTexture.GetPixel(i, j).a > 0.5f)
{
renderedTexture.SetPixel(i, j, renderedTexture.GetPixel(i, j));
}
else
{
renderedTexture.SetPixel(i, j, new Color(0.0f, 0.0f, 0.0f, 0.0f));
}
}
}
renderedTexture.Apply();
yield return renderedTexture;
File.WriteAllBytes(Path.Combine(Application.dataPath, "YOUR_FILE_NAME"), renderedTexture.EncodeToPNG());
}
(Warning: untested code)
Also keep in mind to try to provide what you already tried before asking questions: it helps other users understand what and how you want to achieve it.
Hope this helps,
my world is world = new World(new Vector2(0, 0), true); //there is no gravity
and i have walls in the left, right, top bottom of the screen, so elements do not fly off the display.
I have the following code in my render class:
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
camera.update();
Box2DDebugRenderer debugRenderer = new Box2DDebugRenderer();
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(texture, 0, 0);
for (Element element : elements) {
element.body.setUserData(element);
element.rect.x = element.body.getPosition().x-16;//16 = half the width of element
element.rect.y = element.body.getPosition().y-16;
if (element.goodOrEvil == 0) {
batch.draw(happyImage, element.rect.x, element.rect.y);
} else if (element.goodOrEvil == 1) {
batch.draw(sadImage, element.rect.x, element.rect.y);
}
} //i draw the elements that i have to fix the bodies to
batch.end();
debugRenderer.render(world, camera.combined);
Iterator<Element> iter = elements.iterator();
//next i set the accelerometer to move the elements, so that i could control them, i need to move all the elements in the same direction, in the same time
while (iter.hasNext()) {
Element element = iter.next();
element.body.setLinearVelocity(Gdx.input.getAccelerometerY() * 50, -Gdx.input.getAccelerometerX() * 50);
}
if (TimeUtils.nanoTime() - lastDropTime > 1000000000)
spawnElement(); // this creates a new element
world.step(1 / 45f, 6, 2);
Here is my spawnElement() class:
private void spawnElement() {
Rectangle elementRect = new Rectangle();
elementRect.x = MathUtils.random(0, 800 - 32);
elementRect.y = 400;
elementRect.width = 32;
elementRect.height = 32;
Element element = new Element(elementRect, (int) elementRect.x % 2);
elements.add(element);
// First we create a body definition
BodyDef bodyDef = new BodyDef();
// We set our body to dynamic, for something like ground which doesnt
// move we would set it to StaticBody
bodyDef.type = BodyType.DynamicBody;
// Set our body's starting position in the world
bodyDef.position.set(elementRect.x, elementRect.y);
// Create our body in the world using our body definition
body = world.createBody(bodyDef);
// Create a circle shape and set its radius to 6
CircleShape circle = new CircleShape();
circle.setRadius(6f);
// Create a fixture definition to apply our shape to
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = circle;
fixtureDef.density = 0.5f;
fixtureDef.friction = 0.4f;
fixtureDef.restitution = 0.6f; // Make it bounce a little bit
// Create our fixture and attach it to the body
Fixture fixture = body.createFixture(fixtureDef);
element.body = body;
lastDropTime = TimeUtils.nanoTime();
}
Now if i set gravity to exist world = new World(new Vector2(-2, -20), true); and comment out this line: element.body.setLinearVelocity(Gdx.input.getAccelerometerY() * 50, -Gdx.input.getAccelerometerX() * 50); the physics works great, but i cannot control my objects. If i leave the code as it is (no gravity and that line in), i can control my objects, but when an element hits the wall, and another element comes, they overlap, and only then start to divide. I need them to hit one another, and remain close, but not overlap at all. Any ideea on how this could be made?
instead of using element.body.setLinearVelocity(Gdx.input.getAccelerometerY() * 50, -Gdx.input.getAccelerometerX() * 50);
i have used this:
Vector2 gravity = new Vector2(Gdx.input.getAccelerometerY() * 50, -Gdx.input.getAccelerometerX() * 50);
world.setGravity(gravity);
And then it answers just like when i set a static gravity, but i change the gravity at every render, with values from my accelerometer