I am working on a simple game which requires 1 player (the square) and some enemies that spawn randomly inside the play-area. I am running into an issue currently, because when I run my program, pressing any arrow key will repaint not only the player's new location, but it will also re-spawn all the enemies into the new locations.
I have gone through my code a few times and I am still stumped as to why this is happening. Any help would be greatly appreciated.
P.S. I am not a very experienced programmer, so some of this code may not be as efficient as possible and some things may be incorrect; feel free to point out any errors besides the issue at hand. Thanks!
Main Class
public class Eat {
public static void main(String[] args) {
// Creating the main frame
JFrame main = new JFrame("Eat 'Em All - Version 1.0.2");
main.setSize(497, 599);
main.setLocationRelativeTo(null);
main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main.setResizable(false);
// Colours and borders
Border areaBorder = new LineBorder(Color.LIGHT_GRAY, 3);
// Creating main JPanel
JPanel area = new JPanel();
area.setLayout(new BoxLayout(area, BoxLayout.PAGE_AXIS));
area.setBackground(Color.WHITE);
main.setContentPane(area);
// Creating the drawing/image/player
DrawPlayer player = new DrawPlayer();
player.setPreferredSize(new Dimension(497, 539));
player.setOpaque(false);
// Enemies
DrawEnemy enemy = new DrawEnemy();
enemy.setPreferredSize(new Dimension(497, 539));
enemy.setBackground(Color.WHITE);
// Creating the control panel for buttons, etc
JPanel control = new JPanel();
control.setPreferredSize(new Dimension(497, 60));
control.setLayout(new GridLayout(1, 2, 0, 0));
control.setBorder(areaBorder);
JLabel welcome = new JLabel(" Welcome to Eat 'Em All |--| Press 'Start'");
JButton start = new JButton("Start");
// Adding it all to the frame
main.add(enemy);
enemy.add(player);
control.add(welcome);
control.add(start);
area.add(control);
// Adding keylistener and making button false
player.addKeyListener(player);
player.setFocusable(true);
start.setFocusable(false);
enemy.setFocusable(false);
// Bring frame to front and visible
main.toFront();
main.setVisible(true);
System.out.println(player.getWidth() / 2);
System.out.println(player.getHeight() / 2);
}
}
Drawing Player Class
public class DrawPlayer extends JPanel implements KeyListener {
long xPosition = 0;
long yPosition = 0;
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Making loop to get points and move it
// Center of area is x: 245 y: 255
int xPoints[] = {235, 255, 255, 235, 235, 255};
int yPoints[] = {265, 265, 245, 245, 265, 245};
for (int i = 0; i < xPoints.length; i++) {
xPoints[i] += xPosition;
yPoints[i] += yPosition;
}
g.setColor(Color.BLUE);
g.drawPolygon(xPoints, yPoints, xPoints.length);
}
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN:
if (yPosition == 245) {
yPosition -= 5;
} else {
yPosition += 5;
}
break;
case KeyEvent.VK_UP:
if (yPosition == -245) {
yPosition += 5;
} else {
yPosition -= 5;
}
break;
case KeyEvent.VK_LEFT:
if (xPosition == -235) {
xPosition += 5;
} else {
xPosition -= 5;
}
break;
case KeyEvent.VK_RIGHT:
if (xPosition == 235) {
xPosition -= 5;
} else {
xPosition += 5;
}
break;
}
repaint();
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
Drawing Enemies Class
public class DrawEnemy extends JPanel {
public void paintComponent(Graphics f) {
super.paintComponent(f);
for (int i = 0; i < 10; i++ ){
f.setColor(Color.RED);
f.drawOval((int)(Math.random() * ((440 - 0) + 0) + 0), (int)(Math.random() * ((500 - 0) + 0) + 0), 50, 50);
}
}
}
Your have a problem here:
public void paintComponent(Graphics f) {
super.paintComponent(f);
for (int i = 0; i < 10; i++ ){
f.setColor(Color.RED);
f.drawOval((int)(Math.random() * ((440 - 0) + 0) + 0), (int)(Math.random() * ((500 - 0) + 0) + 0), 50, 50);
}
}
You've got program logic inside of a painting method, something you should never do, since you never have full control over when or even if a painting method will be called. The solution, get the randomization out of the paintComponent method and into its own separate method, one that you call if and only if you want to randomize the enemies, and not every time you repaint.
Other issues:
Separate your program logic from your GUI.
For instance you should have a non-GUI Enemy class, one that has fields for its own position, its size, its movement, perhaps a move() method, perhaps a collision(Player p) method.
You should have only one JPanel that does drawing and this should be its only job.
Again, you don't tie movement of anything to the painting method.
You would want a game loop of some sort, perhaps a Swing Timer. This will generate regularly spaced ticks that will prod Enemies and Players to move.
Get rid of KeyListener code and favor Key Bindings. The latter is much less kludgy when it comes to component focus. Do check the tutorial for this.
Related
I had glitching in speech marks because I am farily sure it is not a glitch.
I have just recently started to code using JFrames, in fact I started Java at school couple months ago, but recently I have tried to push my understanding using these handy frames.
I made a program which would bounce a ball around the frame, and I wanted to make it so there would be 2 (for now they would not collide) however whenever I try to add another it simply shows one.
Here is the code:
public static void main(String[] args) throws InterruptedException{
JFrame frame = new JFrame("Hello There");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
frame.setLayout(new FlowLayout());
Main ball = new Main();
Main ball2 = new Main();
ball2.SetValues(200, 200, Color.BLUE);
frame.add(ball2);
frame.add(ball);
while (true) {
ball.move();
ball.repaint();
ball2.move();
ball2.repaint();
Thread.sleep(5);
}
}
public void move() {
x = x + xDirection;
y = y + yDirection;
if (x < 0) { //If Ball has gone off the screen on x direction
xDirection = 1; //Start moving to the right
} else if (x > getWidth() - 50) { //If x has gone off the screen to the right
xDirection = -1;//Start moving to the left
}
if (y < 0) { //If Ball has gone off the screen on x direction
yDirection = 1; //Start moving to the right
} else if (y > getHeight() - 50) { //If x has gone off the screen to the right
yDirection = -1;//Start moving to the left
}
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(color);
g.fillOval(x, y, 50, 50);
}
I recently added the line "frame.setLayout(new FlowLayout()" and it appears to show the 2 balls but they are in a glitched state.
Could anyone help me out?
You should not sleep or do any long process on Swing thread, nor should you update the gui from other threads. See Concurrency in Swing
One alternative is to use javax.swing.Timer :
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ball.move();
ball.repaint();
ball2.move();
ball2.repaint();
}
});
timer.start();
Another alternative is to use a separate thread
It is also recommended to read Performing Custom Painting
I'm trying to make a hex board with hex images (720x835 GIF) on a scroll-able JPanel. I've overridden the paintComponent method to draw the tiles at different specific locations and used a timer to call repaint at each tick.
When repaint() is called, doDrawing is called. When doDrawing is called, choseTile is called to draw the tiles with drawImage.
For some reason, the tiles are not being drawn and I'm left with an empty black panel. Why are my images not being drawn? Is it because the images are too large? The panel is too large?
public class MapPanel extends JPanel {
// Images for the tiles
Image tile1;
Image tile2;
//etc
// measurements for the tiles
int tileX = 720;
int tileY = 835;
int dimensionX = 14760;
int dimensionY = 14613;
//Use this to keep track of which tiles goes where on a 20x20 board
public int[][] hexer;
/**
* Create the panel.
*/
public MapPanel(int[][] hexMap) {
hexer = hexMap;
setPreferredSize(new Dimension(dimensionX, dimensionY));
setBackground(Color.black);
setFocusable(true);
loadImages();
Timer timer = new Timer(140, animatorTask);
timer.start();
}
//getting the images for the tiles
private void loadImages() {
// Setting the images for the tiles
ImageIcon iid1 = new ImageIcon("/Images/tiles/tile1.gif");
tile1 = iid1.getImage();
ImageIcon iid2 = new ImageIcon("/Images/tiles/tile2.gif");
tile2 = iid2.getImage();
//etc
}
// Drawing tiles
private void choseTile(Graphics g, int x, int y, int id) {
switch (id) {
case 1:
g.drawImage(tile1, x, y, this);
break;
case 2:
g.drawImage(tile2, x, y, this);
break;
//etc
}
}
// repainting stuff
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
int actualX;
int actualY;
//set the painting coordinates and image ID then call the method to draw
for (int x = 0; x < 20; x++) {
for (int y = 0; y < 20; y++) {
if ((y + 1) % 2 == 0) {
actualX = x * tileX + 720;
} else {
actualX = x * tileX + 360;
}
if((x + 1) % 2 == 0){
actualY = (y/2) * 1253 + 418;
}else{
actualY = (y+1)/2 * 1253 + 1044;
}
if(hexer[x][y] != 0)
choseTile(g, actualX, actualY, hexer[x][y]);
}
}
}
private ActionListener animatorTask = new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
};
}
Edit: I've already checked to make sure the images aren't null.
Following Andrew Thompson's suggestion; I used ImageIO. I was able to figure out that the way I was accessing the image files was faulty thanks to thrown errors by ImageIO.
first of all thanks for taking the time to read my question!
I'm developing a 2D java game, with a tile based map. When my character moves around,
everthing is fine, although when he moves left, vertical white lines, also know as artifacting/tearing, appear on the screen. Same thing happens when he moves up, though the lines are horizontal and much smaller in width. Oddly enough, this doesn't happen when I move right or down. I have searched around on internet to find a solution, though I haven't encountered anything that fit my problem.
Here's the code, although I've heavily downsized and simplified it for the sake of testing. It can therefore be run without any images. Thank you for any answer you provide!
package adriana;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
/**
*
* #author Christophe
*/
public class Main extends JFrame implements Runnable{
public Image dbImage;
public Graphics dbGraphics;
//Image + Array size
final static int listWidth = 500, listHeight = 500;
//Move Variables
int playerX = 320, playerY = 240, xDirection, yDirection;
//Sprites
BufferedImage spriteSheet;
//Lists for sprite sheet: 1 = STILL; 2 = MOVING_1; 3 = MOVING_2
BufferedImage[] ARCHER_NORTH = new BufferedImage[4];
BufferedImage[] ARCHER_SOUTH = new BufferedImage[4];
BufferedImage[] ARCHER_EAST = new BufferedImage[4];
BufferedImage[] ARCHER_WEST = new BufferedImage[4];
Image[] TILE = new Image[12];
//Animation Variables
int currentFrame = 0, framePeriod = 150;
long frameTicker = 0l;
Boolean still = true;
Boolean MOVING_NORTH = false, MOVING_SOUTH = false, MOVING_EAST = false, MOVING_WEST = false;
BufferedImage player;
//World Tile Variables
//20 X 15 = 300 tiles
Rectangle[][] blocks = new Rectangle[listWidth][listHeight];
int tileX = 250, tileY = 250;
Rectangle playerRect = new Rectangle(playerX + 4,playerY+20,32,20);
//Map Navigation
static final byte PAN_UP = 0, PAN_DOWN = 1, PAN_LEFT = 2, PAN_RIGHT = 3;
final int speed = 8;
public Main(){
this.setTitle("JAVA4K");
this.setSize(640,505);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
addKeyListener(new AL());
for(int y = 0; y < listHeight; y++){
for(int x = 0; x < listWidth; x++){
blocks[x][y] = new Rectangle(x*32-8000, y*32-8000, 32, 32);
}
}
}
//Key Listener
public class AL extends KeyAdapter{
public void keyPressed(KeyEvent e){
int keyInput = e.getKeyCode();
still = false;
if(keyInput == e.VK_LEFT){
navigateMap(PAN_RIGHT);
}if(keyInput == e.VK_RIGHT){
navigateMap(PAN_LEFT);
}if(keyInput == e.VK_UP){
navigateMap(PAN_DOWN);
}if(keyInput == e.VK_DOWN){
navigateMap(PAN_UP);
}
}
public void keyReleased(KeyEvent e){
int keyInput = e.getKeyCode();
setYDirection(0);
setXDirection(0);
if(keyInput == e.VK_LEFT){
}if(keyInput == e.VK_RIGHT){
}if(keyInput == e.VK_UP){
}if(keyInput == e.VK_DOWN){
}
}
}
public void moveMap(){
for(int a = 0; a < 500; a++){
for(int b = 0; b < 500; b++){
if(blocks[a][b] != null){
blocks[a][b].x += xDirection;
blocks[a][b].y += yDirection;
}
}
}
}
public void navigateMap(byte pan){
switch(pan){
default:
System.out.println("Unrecognized pan!");
break;
case PAN_UP:
setYDirection(-1 * speed);
break;
case PAN_DOWN:
setYDirection(+1 * speed);
break;
case PAN_LEFT:
setXDirection(-1 * speed);
break;
case PAN_RIGHT:
setXDirection(+1 * speed);
break;
}
}
public void setXDirection(int xdir){
xDirection = xdir;
if(blocks[0][0] != null) tileX = ((playerRect.x - blocks[0][0].x) / 32)-1;
}
public void setYDirection(int ydir){
yDirection = ydir;
if(blocks[0][0] != null) tileY = ((playerRect.y - blocks[0][0].y) / 32)-1;
}
public void paint(Graphics g){
dbImage = createImage(getWidth(), getHeight());
dbGraphics = dbImage.getGraphics();
paintComponent(dbGraphics);
g.drawImage(dbImage, 0, 25, this);
}
public void paintComponent(Graphics g){
requestFocus();
//Draws tiles and rectangular boundaries for debugging
for(int a = tileX - 18; a < tileX + 20; a++){
for(int b = tileY - 15; b < tileY + 17; b++){
g.setColor(Color.RED);
g.fillRect(blocks[a][b].x, blocks[a][b].y, 32, 32);
g.setColor(Color.BLACK);
g.drawRect(blocks[a][b].x, blocks[a][b].y, 32, 32);
}
}
//Draw player
g.drawRect(playerX, playerY, 40, 40);
repaint();
}
public void run(){
try{
System.out.println("Running");
while(true){
moveMap();
Thread.sleep(13);
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Main main = new Main();
//Threads
Thread thread1 = new Thread(main);
thread1.start();
}
}
You have no synchronization between the thread updating the map and the drawing. So the map can be in inconsistent state during drawing.
A quick fix would be wrapping the map updating and drawing in a synchronized block:
synchronized(blocks) {
// drawing or modification here
}
(or just making the whole methods synchronized)
Also, the other fields (like those modified in the key listener) are also susceptible being in inconsistent state.
There are other problems too:
Don't override paint() of a frame. Instead override paintComponent() of a JPanel. There's no need to create an image at every redraw, the swing painting mechanism is by default double buffered. See custom painting in swing.
Use KeyBindings instead of a KeyListener
Swing components must be accessed (and created) only in the event dispatch thread.
Here is the portion of my code I'm concerned with, When I try to change the color of squaresToDisplay object again using the Timer, it makes the frame white (blank) but it works only 1 time. So when I run this piece of code, it will do what I want 1 time then make the screen blank. I am wondering what causes this specifically. My own assumption is that I might be blocking the EDT when I start the SQTimer, in which case I'm at a loss because I don't know enough java to fix this :/
private Timer SQTimer;
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//Code that removes unrelated things from the frame
final SquareObjects squaresToDisplay = new SquareObjects(x,y);//Creates the Object based on GUI width and height
squaresToDisplay.setFocusable(true);
squaresToDisplay.setVisible(true);//Allows it to be visible
frame.add(squaresToDisplay);//Adds it to the frame
SQTimer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e){
squaresToDisplay.repaint();
System.out.println("Repainted");
}
});
System.out.println("Completed adding pixels");
SQTimer.setRepeats(true);
SQTimer.start();
}
});
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
System.out.println("Beginning of paintComponent");
System.out.println("Completed making the Graphics objects");
for(int i = 0;i<(x*y)/64;i++){
if(xInterceptLocation == 0){
g.fillRect(xInterceptLocation, yInterceptLocation, 8, 8);
xInterceptLocation += 8;
}else{
Color newColor = changingColors();
g.setColor(newColor);
g.fillRect(xInterceptLocation, yInterceptLocation, 8, 8);
xInterceptLocation += 8;
if(xInterceptLocation == 1920){
xInterceptLocation = 0;
yInterceptLocation += 8;
}
}
}
}
Just for clarification, these to methods are in seperate classes, the first one is in a class called GUI while the second one is in a class called SquareObjects
the problem was that the yInterceptLocation was never set back to 0, so when the program repainted, it continued adding 8, thus leaving the screen blank because it was out of the frames bounds.
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
System.out.println("Beginning of paintComponent");
System.out.println("Completed making the Graphics objects");
//yIntercept needs to be reinitialized when the repaint(); is called again
if(yInterceptLocation == 1080){
yInterceptLocation = 0;
}
for(int i = 0;i<(x*y)/64;i++){
if(xInterceptLocation == 0){//If i == 0 then it wont add 8 first (thus preventing a gap)
g.fillRect(xInterceptLocation, yInterceptLocation, 8, 8);
xInterceptLocation += 8;
}else{//Any other time we want to add 8 to space out the squares
Color newColor = changingColors();
g.setColor(newColor);
g.fillRect(xInterceptLocation, yInterceptLocation, 8, 8);
xInterceptLocation += 8;
if(xInterceptLocation == 1920){//if xInterceptLocation = 1920 then it adds 8 to yIntercept and sets x to 0 (creating a new line)
xInterceptLocation = 0;
yInterceptLocation += 8;
}
}
}
}
Credits to HoverCraft Full Of Eels, for noticing it wasn't reinitialized
I'm having trouble simulating a race between two competitors. This is your typical race program where you use a random number generator to determine what "moves" the competitors use. As seen in my code below, the track is composed of 50 rectangles, and the filled in rectangle shows the location of each competitor on the track. Some "moves" make the competitor jump 9 squares to the right, or 2 squares back, for example. When I run the applet, only the initial starting position is displayed; the applet doesn't work. I realize it's a lot of code, but what do I need to do to fix my problem? I'm really stuck at this point. Any help is appreciated. I have can only use AWT, not swing. This is an assignment for class :/ Here is the code:
import java.awt.*;
import java.applet.*;
public class Example extends Applet
{
Image tortoise, hare;
int tortX = 250, hareX = 250;
final int tortY = 100, hareY = 300, WIDTH = 15, HEIGHT = 50;
int turn; String turnNum;
int move; String tMove, hMove;
public void init()
{
tortoise = getImage( getDocumentBase(), "images/tortoise.gif" );
hare = getImage( getDocumentBase(), "images/hare.gif" );
move = 0; turn = 0;
}
public void control()
{
while (( tortX < 985 ) || ( hareX < 985 ))
{
move = (int)(10 * Math.random());
switch (move)
{
case 1:
case 2:
tortX += (3 * WIDTH);
hareX += (9 * WIDTH);
tMove = "Fast Plod"; hMove = "Big Hop";
break;
case 3:
case 4:
case 5:
tortX += (3 * WIDTH);
hareX += WIDTH;
tMove = "Fast Plod"; hMove = "Small Hop";
break;
case 6:
tortX += WIDTH;
if (hareX == 250) {} // DO NOTHING
else if (hareX <= (250 + (11 * WIDTH)))
hareX = 250;
else
hareX -= (12 * WIDTH);
tMove = "Slow Plod"; hMove = "Big Slip";
break;
case 7:
case 8:
tortX += (1 * WIDTH);
if (hareX == 250) {} // DO NOTHING
else if (hareX <= (250 + (WIDTH)))
hareX = 250;
else
hareX -= (2 * WIDTH);
tMove = "Slow Plod"; hMove = "Small Slip";
break;
case 9:
case 10:
if (tortX == 250) {} // DO NOTHING
else if (tortX <= (250 + (5 * WIDTH)))
tortX = 250;
else
tortX -= (6 * WIDTH);
tMove = "Slip"; hMove = "Fall Asleep. Zzz...";
break;
// Hare falls asleep. No action.
}
turn++; turnNum = (turn + "");
repaint();
for (int i = 1; i <= 10; i++)
{
delay();
}
}
tortX = 985; hareX = 985;
repaint();
}
public void paint( Graphics screen )
{
drawRace(screen);
if (tortX >= 985)
{
screen.setFont(new Font("Times New Roman", Font.ITALIC, 48));
screen.drawString("Tortoise Wins", 650, 240);
clearCurrent(screen);
fillNext(screen);
}
else if (hareX >= 985)
{
screen.setFont(new Font("Times New Roman", Font.ITALIC, 48));
screen.drawString("Tortoise Wins", 650, 240);
clearCurrent(screen);
fillNext(screen);
}
else
{
screen.drawString(("Turn " + turnNum), 621, 55);
screen.setFont(new Font("Times New Roman", Font.ITALIC, 12));
screen.drawString(tMove, 59, 65); screen.drawString(hMove, 66, 255);
clearCurrent(screen);
fillNext(screen);
}
stop();
}
public void clearCurrent( Graphics s )
{
s.clearRect(tortX+1, tortY+1, WIDTH-1, HEIGHT-1);
s.clearRect(hareX+1, hareY+1, WIDTH-1, HEIGHT-1);
}
public void fillNext( Graphics s )
{
s.fillRect(tortX+1, tortY+1, WIDTH-1, HEIGHT-1);
s.fillRect(hareX+1, hareY+1, WIDTH-1, HEIGHT-1);
}
public void drawRace( Graphics s )
{
// GENERATES INITIAL GRAPHICS FOR RACE
s.drawRect(250, 100, 750, 50);
s.drawRect(250, 300, 750, 50);
int lineX = 265, lineYi = 100, lineYf = 150;
for (int i = 1; i <= 98; i++)
{
if (lineX == 1000)
{
lineX = 265; lineYi = 300; lineYf = 350;
}
s.drawLine(lineX, lineYi, lineX, lineYf);
lineX += 15;
}
s.fillRect(tortX+1, tortY+1, WIDTH-1, HEIGHT-1);
s.fillRect(hareX+1, hareY+1, WIDTH-1, HEIGHT-1);
s.drawImage(tortoise, 59, 80, this);
s.drawImage(hare, 66, 271, this);
s.setFont(new Font("Times New Roman", Font.BOLD, 24));
s.drawString("Race", 250, 55);
}
public void delay()
{
for (int i = 0; i < 90000000; i++)
{
}
}
public void stop()
{
}
}
Your first problem is you actually never "start" the race...Sure your init the applet, but then, nothing...
Your second problem is the control method is block the Event Dispatching Thread, this means, they while you are in this method NOTHING will get painted to the screen. This is because the Event Dispatching Thread is also responsible for dispatching repaint requests.
You third problem is your violating the paint contact. You have a responsibility to call super.paint(screen) - paint is a complex method and should never ignore it unless you have a REALLY good reason do to so.
Your fourth problem is, you using an Applet instead of a JApplet. Better to ignore the AWT controls in favor of the Swing controls. Swing are more flexible and easier to extend.
Your fifth problem is you painting onto a top level container, this is never recommended. You are better using something like JPanel and overriding it's paintComponent method (don't forget to call super.paintComponent). Apart from everything else, it's double buffered and will reducing flicking as the screen is updated.
Take a look at...
Concurrency in Swing and How to use Swing Timer to solve you EDT blocking issues...
Performing Custom Painting for ideas about painting in Swing