I have created a custom View.
Just imagine a Rect drawn at its Canvas
at x = 100, y = 100, width = 100, height = 100
in bright blue (or any color you like)
Now I switch off the statusbar -> the App is running in fullscreen.
Just imagine, I would touch exactly in the middle of the rectangle.
The coordiantes should be 150, 150 -- and as they are supposed to be, they are 150, 150.
But now I activate the statusbar -> the canvas shrinks a bit
-> if you look at the screen, the rect is drawn 150px below the statusbar -- this is what I wanted.
Now I touch again exactly in the middle of the rectangle.
The coordiantes should be 150, 150,
but.. -- Jesus! --
The onTouchEvent coordinates include the statusbar height!
And the Canvas coordinates do not!
So what can I do to get the right touch coordinates?
What methods do you used to get the x and y from MotionEvent? Have you tried getRawX() and getRawY()?
---update--
Sorry I think I've misunderstood something. What methods do you used? getY() or getRawY()?
---update again---
Are you handling onTouchEvent in Activity? Here I write some code for testing:
public class CustomView extends View {
private final Paint p = new Paint();
private final Paint p2 = new Paint();
private float lastRawX = 0, lastRawY = 0;
private float lastX = 0, lastY = 0;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
p.setColor(0xFFFF0000);
p2.setColor(0xFF00FF00);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
lastX = event.getX();
lastY = event.getY();
lastRawX = event.getRawX();
lastRawY = event.getRawY();
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
// make the touch point big enough to be noticed.
canvas.drawCircle(lastX, lastY, 3, p);
canvas.drawCircle(lastRawX, lastRawY, 3, p2);
}
}
A red dot for getY() and a green dot for getRawY(). I've tested it in emulator(which I can use the mouse for simulating "accurate" finger touch) and in real device. In both I can see the red dot is drawn exactly at the place I touched and the green dot is drawn below the red one (exactly the height of the status bar).
But if handling onTouchEvent() in an activity, then getY() and getRawY() returns the same value.
Related
I have a picture then used a flashlight type of light to only show where the mouse is hovering over. That part of the code works, but now I want to use if/else statements to zoom in on the selected area and then click again to zoom back out. Any other way to zoom in on specific area then back out of that area also helps. Really any help will be appreciated!
PImage ispy;
void setup () {
size(1024,768);
ispy = loadImage("ispy2.jpeg");
}
void draw () {
loadPixels();
ispy.loadPixels();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x+y*width;
float r = red(ispy.pixels[loc]);
float g = green(ispy.pixels[loc]);
float b = blue(ispy.pixels[loc]);
float d = dist(mouseX, mouseY, x, y); //
float factor = map(d, 0, 200, 2, 0);
pixels[loc] = color(r*factor, g*factor, b*factor);
}
}
updatePixels();
}
Here is my interpretation of what you are talking about. We store a isClicked boolean to store the state of whether we should zoom in or not. When we are going to draw the image, we translate() to the mouse, then we scale(), then we translate() back the same amount that we moved before, but in the opposite direction. What this does is it does the scale transform around the mouse position.
One thing that I couldn't find a way around way your way of updating the pixels directly from the image and the flashlight effect. What the program is doing instead is using your method to make a mask image and applying that to a PGraphics object. Another thing that I noticed is that when just rendering straight to the screen, there is considerable lag from the scaling. Instead, I have moved the drawing to a PGraphics object. This improves the performance.
In the end, to render, the program is drawing everything on the PGraphics object, then applying the mask to that object to get the flashlight effect.
Here is the code that I have:
PImage ispy, distMask;
boolean isClicked = false;
PGraphics renderer;
void createDistanceMask(PImage distMask){ //Takes image and changes its pixels to "alpha" for the PGraphics renderer
distMask.loadPixels();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x+(height-y-1)*width;
float d = dist(mouseX, mouseY, x, y); //
int factor = int(map(d, 0, 200, 400, 0)); //Pixel data will be between 0 and 255, must scale down later.
if (factor > 255)
factor = 255;
distMask.pixels[loc] = color(factor,factor,factor);
}
}
distMask.updatePixels();
}
void setup () {
size(1024,768, P2D);
ispy = loadImage("ispy2.jpeg");
distMask = new PImage(width,height);
renderer = createGraphics(width,height,P2D);
mouseX = width/2; //Not necessary, but will have black screen until mouse is moved
mouseY = height/2;
}
void draw () {
background(0);
pushMatrix();
createDistanceMask(distMask);
renderer.beginDraw(); //Starts processing stuff into PGraphics object
renderer.background(0);
if(isClicked){ //This is to get the zoom effect
renderer.translate(mouseX, mouseY);
renderer.scale(2);
renderer.translate(-mouseX, -mouseY);
}
renderer.image(ispy,0,0); //Render Image
renderer.endDraw();
renderer.mask(distMask); //Apply Distance mask for flashlight effect
image(renderer,0,0); //Draw renderer result to screen
popMatrix();
}
void mouseClicked(){
isClicked = !isClicked;
}
In my comment, I asked about having the screen move to the mouse, which is what this is doing. If you want to "freeze" the screen in one position, what you can do is store a lastMouseClickPosition PVector or simply just ints. Then, when translating, translate to the position instead of the PVector.
Here's the code that would change:
PVector lastClickPos = new PVector(); //Make the position
if(isClicked){ //When Rendering
renderer.translate(lastClickPos.x, lastClickPos.y);
renderer.scale(scalingFactor);
renderer.translate(-lastClickPos.x, -lastClickPos.y);
}
void mouseClicked(){ //At the bottom
isClicked = !isClicked;
lastClickPos.set(mouseX, mouseY);
}
I am programming a 2d platformer with libgdx, I'm trying to make a menu screen where the player can click a button and it will load that level. I use gdx.input for the click coordinates and TextureRegion.getRegionX() for the button coordinates. They don't sync together and I read that camera.unproject should fix this problem. I duly used it but the coords still don't match. camera.unproject seems to set 0,0 for x and y as the centre of the screen, while batch.draw (which is the method which draws the TextureRegion to screen) seems to be using the bottom left hand corner as x and y's 0, 0.
Here is the code, I left out what I didn't think was relevant:
public class LevelScreen implements Screen {
private TextureRegion level_bg;
private SpriteBatch batch;
private Camera camera;
private TextureAtlas textureAtlas;
private TextureRegion lockselectbg[]=new TextureRegion[10];
public LevelScreen(){
}
#Override
public void show() {
batch=new SpriteBatch();
camera = new OrthographicCamera(500,700);
LevelStatus.put();
LevelStatus.get();
textureAtlas=new TextureAtlas(Gdx.files.internal("levelatlas.pack"));
Array<AtlasRegion> atlasArrays = new Array<AtlasRegion>(textureAtlas.getRegions());
level_bg = atlasArrays.get(0);
lockselectbg[0] = atlasArrays.get(21);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(159/255.0f,220/255.0f,235/255.0f,0xff/255.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(level_bg, -500/2,-348/2);
batch.draw(lockselectbg[0], -180,0);
batch.end();
if(Gdx.input.isTouched()){
Vector3 tmp = new Vector3(Gdx.input.getX(),Gdx.input.getY(), 0);
camera.unproject(tmp);
Rectangle textureBounds = new Rectangle(lockselectbg[0].getRegionX(), lockselectbg[0].getRegionY(), lockselectbg[0].getRegionWidth(), lockselectbg[0].getRegionHeight());
if(textureBounds.contains(tmp.x, tmp.y)) {
System.out.println("It worked");
}
}
}
#Override
public void dispose() {
textureAtlas.dispose();
batch.dispose();
}
Camera#unproject will convert touch coordinates to world coordinates. They have nothing to do with the location of the region on the texture, which is what TextureRegion is. You are practically comparing world (read: game logic) coordinates with asset coordinates. Those two are completely unrelated.
If you want to check whether your image on the screen is touched then compare the touch coordinate with the location and size of the image you used in the batch.draw call. For example:
float x = -180f;
float y = 0f;
float width = 200f;
float height = 150f;
...
batch.draw(region, x, y, width, height);
...
camera.unproject(tmp.set(Gdx.input.getX(),Gdx.input.getY(), 0));
boolean touched = tmp.x >= x && tmp.y >= y && tmp.x < (x + width) && tmp.y < (y + height);
if (touched)
System.out.println("It worked");
Btw, you might want to read this post: http://blog.xoppa.com/pixels as well, because you are coupling your logic with asset size.
This question already has an answer here:
Clear portion of graphics with underlying image
(1 answer)
Closed 7 years ago.
I'm in the process of making a 2D game in which a player roams around a maze.
I want to implement some sort of "darkness", even something as simple as a transparent shape around the player surrounded by black, like this:
The problem I've found using Swing is that, while this is possible, it means having to redraw everything, which produces an annoying "flickering" effect every time it happens. Is there a way to make some sort of overlay, or just a good way of doing this in general in Swing? I'm not very experienced with GUI/visual stuff right now so I'd like to stick with Swing if possible.
EDIT: This is my method to paint the background, i.e. the floor, walls and exit:
public final void paintBG(Graphics g){
g.setColor(Color.LIGHT_GRAY); // Screen background
g.fillRect(0, 0, getWidth(), getHeight());
// Draw the Walls of the maze
// scalex and y are for scaling images/walls within the maze since I let users specify how big they want the maze
for (int j = 0; j < this.height; j++, y += scaley) {
x = 20;
for (int i = 0; i < this.width; i++, x += scalex) {
if (!(maze[j][i].northwall.isBroken())) // If the north wall isn't broken
{
g.drawImage(walltile, x, y, scalex, scaley / 5, null); // Draw a wall there (image, xpos, ypos, width, height, observer)
}
if (!(maze[j][i].eastwall.isBroken())) // etc
{
g.drawImage(walltile, x + scalex, y, scalex / 5, scaley, null);
}
if (!(maze[j][i].southwall.isBroken())) {
g.drawImage(walltile, x, y + scaley, scalex, scaley / 5, null);
}
if (!(maze[j][i].westwall.isBroken())) {
g.drawImage(walltile, x, y, scalex / 5, scaley, null);
}
if ((j == mazeinfo.getTargetM()) && (i == mazeinfo.getTargetN())) {
// Draw the exit
g.drawImage(jeep, x + (scalex / 2), y + (scaley / 2), cx, cy, null);
g.setColor(Color.LIGHT_GRAY);
if (maze[j][i].northwall.isEdge()) {
// Paint over the edge creating a 'way out'
g.fillRect(x, y, scalex, scaley / 4);
} else if (maze[j][i].eastwall.isEdge()) {
g.fillRect(x + scalex, y, scalex / 4, scaley);
} else if (maze[j][i].southwall.isEdge()) {
g.fillRect(x, y + scaley, scalex, scaley / 4);
} else if (maze[j][i].westwall.isEdge()) {
g.fillRect(x, y, scalex / 4, scaley);
}
}
}
}
}
I then have "paintPlayer" and "paintEnemy" methods to paint those sprites each time they move. The background only gets painted once, at the start.
Possibilities:
You may be drawing directly in a top level window such as a JFrame. If so, don't draw in the paintComonent method of a JPanel so that you use the automatic double buffering availabe.
You may be reading in an image from within a painting method, and if so, don't. These methods must paint and paint only and must be blindingly fast.
You may not be using a BufferedImage in your painting method but creating an image de-novo, and if so, don't. Draw the BufferedImage using Graphics#drawImage(...).
Perhaps your animation code is off. You may be calling repaint() from within paint or paintComponent, something that should never be done.
And the possible guesses can go on and on...
Edit
Your code shows that you may be re-paint the maze with every painting iteration -- don't do this. Instead draw the above into a BufferedImage, and draw that image within your paintComponent method. Then change the BufferedImage if the walls structurally change.
Note that the maze's logical structure (the non-visual data that tells which wall is open, which is closed) should be part of your program's data, and not its code.
Here in an example of using a LayerUI from Oracle's Swing UI documentation. Just change the AlphaComposite constant to something darker.
The following is a LayerUI subclass that draws a translucent circle wherever the mouse moves inside a panel.
class SpotlightLayerUI extends LayerUI<JPanel> {
private boolean mActive;
private int mX, mY;
#Override
public void installUI(JComponent c) {
super.installUI(c);
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK
);
}
#Override
public void uninstallUI(JComponent c) {
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(0);
super.uninstallUI(c);
}
#Override
public void paint (Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D)g.create();
// Paint the view.
super.paint (g2, c);
if (mActive) {
// Create a radial gradient, transparent in the middle.
java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
float radius = 72;
float[] dist = {0.0f, 1.0f};
Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
RadialGradientPaint p =
new RadialGradientPaint(center, radius, dist, colors);
g2.setPaint(p);
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .6f));
g2.fillRect(0, 0, c.getWidth(), c.getHeight());
}
g2.dispose();
}
#Override
protected void processMouseEvent(MouseEvent e, JLayer l) {
if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
l.repaint();
}
#Override
protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
mX = p.x;
mY = p.y;
l.repaint();
}
}
To keep the spotlight's center updated on your player, create an event for player movement and register the LayerUI to listen for updates. See the setLayerEventMask() example in the JLayer link below.
source: How to Decorate Components with the JLayer Class
I successfully added an oval shape to the canvas, however now i'd like to add two more rectangles but for some reason they do not get added to the canvas. The oval shape is a ball which moves and rectangle shapes are static elements which are for "background". One rectangle should be as a floor and another one as an obstacle for the moving object, ball.
I tried to visualize it in the image:
This is the code, mBack and mObs are the rectangles i'm trying to add.
AnimatedView animatedView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
ShapeDrawable mBack = new ShapeDrawable();
ShapeDrawable mJump = new ShapeDrawable();
public static int x;
public static int y;
public class AnimatedView extends ImageView {
static final int width = 50;
static final int height = 50;
public AnimatedView(Context context) {
super(context);
// TODO Auto-generated constructor stub
mDrawable = new ShapeDrawable(new OvalShape());
mBack = new ShapeDrawable(new RectShape());
mObs = new ShapeDrawable(new RectShape());
mDrawable.getPaint().setColor(0xffffAC23);
//mDrawable.setBounds(x, y, x + width, y + height);
mDrawable.setBounds(y, x, y + width, x + height);
mBack.setBounds(100, 100, 100, 100);
mObs.setBounds(120,120,120,120);
}
#Override
protected void onDraw(Canvas canvas) {
mDrawable.setBounds(y, x, y + width, x + height);
mBack.draw(canvas);
mDrawable.draw(canvas);
invalidate();
}
}
mDrawable will be added however mBack or mObs not. Adding setBounds to onDraw won't change a thing also.
The way you are setting Bounds is wrong. The definition of the setBounds method is here:
setBounds(int left, int top, int right, int bottom)
for the two rectangles you are setting it as
mBack.setBounds(100, 100, 100, 100);
mObs.setBounds(120,120,120,120);
This means you are left and right corners are same and top and bottom are same so you are not seeing your rectangle.
Set it something like this then you will see your rectangles
mBack.setBounds(100, 100, 300, 400);
And call draw method on both rectangle shapes in onDraw method.
It seems the problem is that for mBack you defined the bounds to zero pixel (start end ends in (100,100)), same for mObs, but for that you didn't call draw either.
I'm trying to make an object appear where the person last touched. However when I try to do so it appears in the wrong place. I assume this is because of the fact the coordinates that the input returns is different to the display coordinates, my code is as follows:
public class Core implements ApplicationListener, InputProcessor
{ //Has to be here otherwise the code formatting becomes buggy
private Mesh squareMesh;
private PerspectiveCamera camera;
private Texture texture;
private SpriteBatch spriteBatch;
Sprite sprite;
float moveX = 0;
private final Matrix4 viewMatrix = new Matrix4();
private final Matrix4 transformMatrix = new Matrix4();
#Override
public void create()
{
Gdx.input.setInputProcessor(this);
texture = new Texture(Gdx.files.internal("door.png"));
spriteBatch = new SpriteBatch();
sprite = new Sprite(texture);
sprite.setPosition(0, 0);
viewMatrix.setToOrtho2D(0, 0, 480, 320);
float x = 0;
float y = 0;
}
#Override
public void dispose()
{
}
#Override
public void pause()
{
}
#Override
public void render()
{
viewMatrix.setToOrtho2D(0, 0, 480, 320);
spriteBatch.setProjectionMatrix(viewMatrix);
spriteBatch.setTransformMatrix(transformMatrix);
spriteBatch.begin();
spriteBatch.disableBlending();
spriteBatch.setColor(Color.WHITE);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
//spriteBatch.draw(texture, 0, 0, 1, 1, 0, 0, texture.getWidth(),
// texture.getHeight(), false, false);
sprite.draw(spriteBatch);
spriteBatch.end();
update();
}
#Override
public void resize(int width, int height)
{
float aspectRatio = (float) width / (float) height;
camera = new PerspectiveCamera(67, 2f * aspectRatio, 2f);
}
#Override
public void resume()
{
}
public void update()
{
float delta = Gdx.graphics.getDeltaTime();
if(Gdx.input.isTouched())
{
Vector3 worldCoordinates = new Vector3(sprite.getX(), sprite.getY(), 0);
camera.unproject(worldCoordinates);
sprite.setPosition(Gdx.input.getX(), Gdx.input.getY());
float moveX = 0;
float moveY = 0;
}
}
I cropped this code for sake of simplicty.
I also made a video demonstrating the bug:
http://www.youtube.com/watch?v=m89LpwMkneI
Camera.unproject converts screen coordinates to world coordinates.
Vector3 pos = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(pos);
sprite.setPosition(pos.x, pos.y);
Firstly, Gdx.input.getX() and Gdx.input.getY() return "screen coordinates". You want to transform these to your "camera coordinates". Screen coordinates typically have (0,0) in the top left corner of the window. I think your camera coordinates have (0,0) at the bottom left corner (either libgdx or opengl are doing that). Your video seems to suggest that this true. So you will need to multiply the Y value by -1. Secondly, I suspect the scale of the screen is different from the scale of the camera. I think you can fix the scale by multiplying by (world/screen).
Let's say your screen has width=800, height=600 and your world has width=480 height=320. Then your new X,Y for your sprite should be:
X = Gdx.input.getX()*(480/800)
Y = Gdx.input.getY()*(320/600)*-1
you should check your touch cordinates.
Gdx.app.log("", "hello x"+touchx);
Gdx.app.log("", "hello x"+touchy);
here touchx and touchy are your input x and input y variables
then do calculation where touch should work
like if u touched x=100,y=100
and touchx is coming 120
and touch y is coming 120
soo in your update method do this
sprite.setPosition(Gdx.input.getX()-20, Gdx.input.getY()-20);
i think this will help
I figured out the screen size/ game ratio and multiplied it to the current screen size:
rect.x=((((1024/Gdx.graphics.getWidth()))* Gdx.graphics.getWidth())
for a screen width of 1024 pixels
There must be an easier way however this works for me.