I'm new to Android programming and now I'm trying to make a simple Sea Battle game for one person. Ships are places, player hits the field and see whether the shot hit or not.
Basically, the field looks like this:
The code is:
public void onDraw(Canvas canvas) {
if (getWidth() > getHeight()) {
rebro = getHeight();
} else {
rebro = getWidth(); // the smaller size of screen is "rebro"
}
rebro_piece = rebro / 10; // divide the screen by 10 (to get 10x10 field)
Paint background = new Paint();
background.setColor(getResources().getColor(R.color.game_background));
canvas.drawRect(0, 0, rebro, rebro, background); // draw background
Paint divider = new Paint();
divider.setColor(getResources().getColor(R.color.divider_black));
// drawing divider lines
for (int i=0; i<11; i++) {
canvas.drawLine(0, i*rebro_piece, rebro, i*rebro_piece, divider); // horizontal
canvas.drawLine(i*rebro_piece, 0, i*rebro_piece, rebro, divider); // vertical
}
canvas.drawLine(rebro-1, 0, rebro-1, rebro, divider);
}
That's how I make the "field."
In another class I have a method that collects numbers x and y of a 10×10 array that represents where the ships are placed. For debugging, I need to draw them on my field. Ship coordinates are retrieved in a cycle.
So I wrote a drawShip(int x, int y) method.
On Stack Overflow I've founded a question about "Why I can't paint outside onDraw()?" and I've changed my method to this:
public void drawShip(int x, int y) {
myX = x; //global
myY = y; //global
needToPaintShip = true; //boolean
invalidate(); // refreshing?
needToPaintShip = false;
}
Here needToPaintShip decides whether the redrawing of canvas is needed or not.
Also I've edited the onDraw(Canvas canvas) method:
if(needToPaintShip == true) {
Paint ship = new Paint();
ship.setColor(getResources().getColor(R.color.ship_color));
Log.d(TAG, "onDraw(): rebro_piece = " + rebro_piece + " , myX = "+ myX + " , myY = " + myY); // I only get the last coordinates!
Rect r = new Rect(myX*(rebro_piece),myY*rebro_piece, myX*(rebro_piece+1), myY*(rebro_piece+1));
canvas.drawRect(r, ship);
}
but the result is awful:
Guys, I'm desperate. How can I fix this and make "ships" be drawn on the field?
Why do you set needToPaintShip = false; after calling invalidate()? Don't you need to draw the ship again in subsequent frames?
Also, it seems like this item:
Rect r = new Rect(myX*(rebro_piece),myY*rebro_piece, myX*(rebro_piece+1), myY*(rebro_piece+1));
should probably be:
Rect r = new Rect(myX*(rebro_piece),myY*rebro_piece, (myX+1)*rebro_piece, (myY+1)*rebro_piece));
As for why the ship always appears in the bottom right corner, that depends on what you pass to drawShip(x,y), which isn't shown. Is it possible that you are passing pixel coordinates instead of something in the range [0-10)?
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 clearly missing an important concept here. I have written code using mouse events to draw a boundary (a polygon) on an existing BufferedImage. Here is the relevant section:
public void paintComponent(Graphics g)
{
super.paintComponent(g); //Paint parent's background
//G3 displays the BufferedImage "Drawing" with each paint
Graphics2D G3 = (Graphics2D)g;
G3.drawImage(this.Drawing, 0, 0, null);
G3.dispose();
}
public void updateDrawing()
{
int x0, y0, x1, y1; // Vertex coordinates
Line2D.Float seg;
// grafix is painting the mouse drawing to the BufferedImage "Drawing"
if(this.pts.size() > 0)
{
for(int ip = 0; ip < pts.size(); ip++)
{
x0 = (int)this.pts.get(ip).x;
y0 = (int)this.pts.get(ip).y;
this.grafix.drawRect(x0 - this.sqw/2, y0 - this.sqh/2, + this.sqw, this.sqh);
if (ip > 0)
{
x1 = (int)this.pts.get(ip-1).x;
y1 = (int)this.pts.get(ip-1).y;
this.grafix.drawLine(x1, y1, x0, y0);
seg = new Line2D.Float(x1, y1, x0, y0);
this.segments.add(seg);
}
}
}
repaint();
}
The next two routines are called by the mouse events: Left click gets the next point and right click closes the region.
public void getNextPoint(Point2D p)
{
this.isDrawing = true;
Point2D.Float next = new Point2D.Float();
next.x = (float) p.getX();
next.y = (float) p.getY();
this.pts.add(next);
updateDrawing();
}
public void closeBoundary()
{
//Connects the last point to the first point to close the loop
Point2D.Float next = new Point2D.Float(this.pts.get(0).x, this.pts.get(0).y);
this.pts.add(next);
this.isDrawing = false;
updateDrawing();
}
It all works fine and I can save the image with my drawing on it:
image with drawing
The list of vertices (pts) and the line segments (segments) are all that describe the region/shape/polygon.
I wish to extract from the original image only that region enclosed within the boundary. That is, I plan to create a new BufferedImage by moving through all of the pixels, testing to see if they fall within the figure and keep them if they do.
So I want to create an AREA from the points and segments I've collected in drawing the shape. Everything says: create an AREA variable and "getPathIterator". But on what shape? My AREA variable will be empty. How does the path iterator access the points in my list?
I've been all over the literature and this website as well.
I'm missing something.
Thank you haraldK for your suggestion. Before I saw your post, I came to a similar conclusion:
Using the Arraylist of vertices from the paint operation, I populated a "Path2D.Float" object called "contour" by looping through the points list that was created during the "painting" operation. Using this "contour" object, I instantiated an Area called "interferogram". Just to check my work, I created another PathIterator, "PI", from the Area and decomposed the Area, "interferogram" into "segments" sending the results to the console. I show the code below:
private void mnuitmKeepInsideActionPerformed(java.awt.event.ActionEvent evt)
{
// Keeps the inner area of interest
// Vertices is the "pts" list from Class MouseDrawing (mask)
// It is already a closed path
ArrayList<Point2D.Float> vertices =
new ArrayList<>(this.mask.getVertices());
this.contour = new Path2D.Float(Path2D.WIND_NON_ZERO);
// Read the vertices into the Path2D variable "contour"
this.contour.moveTo((float)vertices.get(0).getX(),
(float)vertices.get(0).getY()); //Starting location
for(int ivertex = 1; ivertex < vertices.size(); ivertex++)
{
this.contour.lineTo((float)vertices.get(ivertex).getX(),
(float)vertices.get(ivertex).getY());
}
this.interferogram = new Area(this.contour);
PathIterator PI = this.interferogram.getPathIterator(null);
//Test print out the segment types and vertices for debug
float[] p = new float[6];
int icount = 0;
while( !PI.isDone())
{
int type = PI.currentSegment(p);
System.out.print(icount);
System.out.print(" Type " + type);
System.out.print(" X " + p[0]);
System.out.println(" Y " + p[1]);
icount++;
PI.next();
}
BufferedImage masked = Mask(this.image_out, this.interferogram);
// Write image to file for debug
String dir;
dir = System.getProperty("user.dir");
dir = dir + "\\00masked.png";
writeImage(masked, dir, "PNG");
}
Next, I applied the mask to the image testing each pixel for inclusion in the area using the code below:
public BufferedImage Mask(BufferedImage BIM, Area area)
{
/** Loop through the pixels in the image and test each one for inclusion
* within the area.
* Change the colors of those outside
**/
Point2D p = new Point2D.Double(0,0);
// rgb should be white
int rgb = (255 << 24);
for (int row = 0; row < BIM.getWidth(); row++)
{
for (int col = 0; col < BIM.getHeight(); col++)
{
p.setLocation(col, row);
if(!area.contains(p))
{
BIM.setRGB(col, row, rgb);
}
}
}
return BIM;
}
public static BufferedImage deepCopy(BufferedImage B2M)
{
ColorModel cm = B2M.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = B2M.copyData(B2M.getRaster()
.createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
This worked beautifully (I was surprised!) except for one slight detail: the lines of the area appeared around the outside of the masked image.
In order to remedy this, I copied the original (resized) image before the painting operation. Many thanks to user1050755 (Nov 2014) for the routine deepCopy that I found on this website. Applying my mask to the copied image resulted in the portion of the original image I wanted without the mask lines. The result is shown in the attached picture. I am stoked!
masked image
I have written a program in which a UFO (in essence, a gray ellipse) appears from the center of the screen and flies to the edge. There is a laser that appears when the mouse is pressed, and disappears when the mouse is released. I want to make it so that the UFO disappears when the mouse clicks on it/the laser touches it.
I've made it as far as to make the UFO class and create variables that determine its movements and speed, and I was able to get the laser to appear directly on the cursor. I thought of making an 'if' statement to check if the cursor is within the radius (or diameter) of the UFO, and placing it inside of the for loop I created for the UFOs. However, I am not sure how to achieve the proper syntax to make it happen.
Note: You may need to wait a few seconds for the first circle to appear after you play the sketch.
UFO[] ufos = new UFO[3];
void setup() {
size(700, 700);
for (int j = 0; j < ufos.length; j++) {
ufos[j] = new UFO();
}
}
//UFO class
//Class setup ends on line 61
class UFO {
float a;
float b;
float c;
float sa;
float sb;
float d;
UFO() {
//declare float a/b/c value
a = random(-width/2, width/2);
b = random(-height/2, width/2);
c = random(width);
}
//UFO movement
void update() {
//float c will serve as a speed determinant of UFOs
c = c - 1;
if (c < 5) {
c = width;
}
}
//UFO setup
void show() {
//moving x/y coordinates of UFO
float sa = map(a / c, 0, 1, 0, width);
float sb = map(b / c, 0, 1, 0, height);
float d = map(c, 0, width, 50, 0);
//UFO drawing shapes
//ellipse is always sa (or sb) / c to allow UFO to appear
//as if it is moving through space
fill(200);
ellipse((sa / c), (sb / c), d + 5, d+5);
//Hides UFO way off the screen
//and replaces it with a black-filled ellipse until
//it forms into a full circle
//When radius d becomes 50, the UFO flies from the
//center of the screen to off of the screen
if (d < 50) {
fill(0);
ellipse(-5, -10, 90, 90);
sa = 10000;
sb = 10000;
}
}
}
void draw() {
//Background
background(0);
//Translated the screen so that the UFOs appear to fly from
//the center of the screen
translate(width/2, height/2);
//UFO draw loop, make UFO visible on screen
for (int j = 0; j < ufos.length; j++) {
ufos[j].update();
ufos[j].show();
//mouse-click laser
if (mousePressed == true) {
fill(200,0,0);
ellipse(mouseX - 352,mouseY - 347,50,50);
}
}
}
Like I said on the Happy Coding forum:
Basically, if your UFO is a series of circles, then you just need to use the dist() function to check whether the distance from the mouse to the center of the circle is less than the radius of the circle. If it is, then the mouse is inside the circle. Here's a small example:
float circleX = 50;
float circleY = 50;
float circleDiameter = 20;
boolean showCircle = true;
void draw(){
background(0);
if(showCircle){
ellipse(circleX, circleY, circleDiameter, circleDiameter);
}
}
void mousePressed(){
if(dist(mouseX, mouseY, circleX, circleY) < circleDiameter/2){
showCircle = false;
}
}
If your UFO is multiple circles, then you need to apply this logic to each circle. Please try something and post a small example like this one (not your whole sketch) if you get stuck. Good luck.
So ive been trying to make a game app that either displays a red button with text or a green button with text randomly on the android screen. If anyone can help me with this i would appreciate it. also on a side note i want to slowly generate faster cool upside if anyone knows how to do that. Thanks!
#SuppressLint("DrawAllocation")
#Override
protected void onDraw(Canvas canvas){
String str = "Joke of the day";
super.onDraw(canvas);
paint = new Paint();
Random random = new Random();
Random randomTwo = new Random();
//Rect ourRect = new Rect();
Rect topRect = new Rect();
Rect backGround = new Rect();
paint.setColor(Color.BLACK);
backGround.set(0,0,canvas.getWidth(),canvas.getHeight());
canvas.drawRect(backGround, paint);
for(int i = 0; i <= 900; i++;){
}
if(blank == time){
paint.setColor(Color.RED);
canvas.drawCircle(random, randomTwo, 230, paint);
}else {
paint.setColor(Color.GREEN);
canvas.drawCircle(random, randomTwo, 230, paint);
}
}
You only need one Random instance.
Declare private long lastUpdated = 0; and private int lastColor = Color.BLACK; outside of the onDraw.
Update the bottom portion to:
final float radius = 230f;
if(System.currentTimeMillis() > lastUpdated + 1000){
lastColor = random.nextInt(2) == 1 ? Color.RED : Color.GREEN;
lastUpdated = System.currentTimeMillis();
}
paint.setColor(lastColor);
canvas.drawCircle(random.nextInt(canvas.getWidth()-radius/2) + radius/2f, random.nextInt(canvas.getHeight()-radius/2) + radius/2f, radius, paint);
This will draw a circle of either red or green at random location every second.
You need the radius/2 because the coordinates are from the center of the circle.
As for your second part of your question, also on a side note i want to slowly generate faster cool upside. You'd have to clarify what you mean.
Edit:
Provided a more complete (and correct) sample here:
https://gist.github.com/mshi/8287fd3956c9a917440d
I am trying to make a custom view for Android. I need a big ractangle that holds 7 other rectangles (equally spaced and padded from inside the main rectangle, representing days of week). With my current code, I get the following result:
But what I am looking for should be (the ratio is not important as long as spaces are equal):
Here is my code. Any help and suggestion will be appriciated!
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Main rectangle
Rect boxMain = new Rect();
boxMain.set(getLeft() + 25, getTop() + 25, getRight() - 25, getBottom() - 25);
int hMiniBox = boxMain.height() / 7; //height for each of 7 rectangles
int space = 10; //Space between each rectangle inside the main rectangle
int rectH = hMiniBox; //Height of each rectangle
//Draw the main rectangle
canvas.drawRect(boxMain, _paintProgressBoxBorder);
//Draw 7 rectangles inside main rectangle
for(int i = 0; i <7; i++)
{
Rect rect = new Rect();
rect.set(
boxMain.left + space,
boxMain.top + space,
boxMain.right - space,
rectH
);
canvas.drawRect(rect, _paintProgressMiniBoxesBorder);
rectH += hMiniBox;
}
invalidate();
}
When you loop through setting the small rect, you set the top as boxMain.top + space every time, and only increment the bottom. So really you are drawing 7 rectangles over the top of each other, but with increasing height each time.
Try something like the following:
int smallRectTop = 0
for(int i = 0; i <7; i++) {
Rect rect = new Rect();
rect.set(
boxMain.left + space,
smallRectTop + space,
boxMain.right - space,
smallRectTop += hMiniBox; // Increment and set rect.bottom at the same time
);
}