The output of the code at the moment is the rectangle design and the first line of the array repeated. The wanted output is the rectangle design and the whole array rather than just the first line.
public class design
{
public static void main (String[] args)
{
JFrame window = new JFrame ("Game Screen");
window.getContentPane ().add (new drawing ());
window.setSize (500, 500);
window.setVisible (true);
window.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
}
}
class drawing extends JComponent
{
public void paint (Graphics g)
{
int[] [] word = {{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 9, 0, 0, 7, 9}};
int r = 0;
int c = 0;
Graphics2D g2 = (Graphics2D) g;
Rectangle rect;
for (int x = 5 ; x < 450 ; x += 50)
{
for (int y = 5 ; y < 450 ; y += 50)
{
rect = new Rectangle (x, y, 50, 50);
g2.draw (rect);
g.drawString (Integer.toString (word [r] [c]), x + 25, y + 25);
}
c++;
if (c == 9)
{
c = 0;
r++;
}
}
rect = new Rectangle (150, 5, 150, 450);
g2.draw (rect);
rect = new Rectangle (5, 150, 450, 150);
g2.draw (rect);
}
}
Try to put r++; in the second for loop after calling g.drawString. r must also be set to 0 inside the first for loop and before entering the second one.
for (int x = 5 ; x < 450 ; x += 50){
r = 0;
for (int y = 5 ; y < 450 ; y += 50){
rect = new Rectangle (x, y, 50, 50);
g2.draw (rect);
g.drawString (Integer.toString (word [r] [c]), x + 25, y + 25);
r++;
}
c++;
}
It would be more readable (and logical in my opinion) to use only two variables for the loops, x and y in your case, and increment them only by one. You can use them to calculate the positions of your rectangles and place your numbers:
for (int x = 0 ; x < 9 ; x++){
for (int y = 0 ; y < 9 ; y++){
rect = new Rectangle (5+x*50, 5+y*50, 50, 50);
g2.draw (rect);
g.drawString (Integer.toString (word [y] [x]), 5+x*50 + 25, 5+y*50 + 25);
r++;
}
c++;
}
Avoid using magic numbers, it's better to define constant variables and give them an appropriate name. You can then easily change their values, and your code is clearer.
What are "magic numbers" in computer programming?
The issue with your logic is that increment of c doesn't follow increment of y(y-axis). i.e. as per your logic c represents y axis in your 2-dimensional array, but you are incrementing it in the for loop of x axis. Solution to this is, move your logic of c increment in inner for loop
for (int x = 5; x < 450; x += 50) {
for (int y = 5; y < 450; y += 50) {
rect = new Rectangle(x, y, 50, 50);
g2.draw(rect);
g.drawString(Integer.toString(word[r][c]), x + 25, y + 25);
c++;
}
if (c == 9) {
c = 0;
r++;
}
}
As your loop will only increment 9 times, moving of if condition along with c++ is not required.
I am trying to make my particle system generate particles one by one, rather than all at the same time. My code currently will generate all 100 particles instantly.
I have not tried much as I am new to coding.
I have a setup where I call and updated my particle class, and a class that has all my parameters of the particle system.
int num = 100;
Particle[] p = new Particle[num];
void setup() {
size(1080, 720);
colorMode(HSB);
for (int i = 0; i < num; i ++) {
p[i] = new Particle(new PVector(random(width), random(height)), 100, 150);
}
stroke(255);
}
void draw() {
background(0);
for (int i = 0; i < num; i ++) {
p[i].update(p, i);
}
}
class Particle {
PVector pos;
PVector vel;
float r, mr;
float spd = 0.1;
float max = 2;
Particle(PVector pos, float r, float mr) {
this.pos = pos;
this.r = r;
this.mr = mr;
vel = new PVector(random(-1, 1), random(-1, 1));
}
void update(Particle[] p, int i) {
float h = map(mouseX, 0, width, 0, 255);
pos.add(vel);
if (pos.x < -10) pos.x = width;
if (pos.x > width + 10) pos.x = 0;
if (pos.y < -10) pos.y = height;
if (pos.y > height + 10) pos.y = 0;
vel.x = constrain(vel.x + random(-spd, spd), -max, max);
vel.y = constrain(vel.y + random(-spd, spd), -max, max);
for (int j = i + 1; j < p.length; j ++) {
float ang = atan2(pos.y - p[j].pos.y, pos.x - p[j].pos.x);
float dist = pos.dist(p[j].pos);
if (dist < r) {
stroke(h, 255, map(dist, 0, r, 255, 0));
strokeWeight(map(dist, 0, r, 3, 0));
line(pos.x, pos.y, p[j].pos.x, p[j].pos.y);
float force = map(dist, 0, r, 4, 0);
vel.x += force * cos(ang);
vel.y += force * sin(ang);
}
}
float ang = atan2(pos.y - mouseY, pos.x - mouseX);
float dist = pos.dist(new PVector(mouseX, mouseY));
if (dist < r) {
stroke(0, 0, map(dist, 0, r, 255, 0));
strokeWeight(map(dist, 0, r, 3, 0));
line(pos.x, pos.y, mouseX, mouseY);
float force = map(dist, 0, r, 30, 0);
vel.x += force * cos(ang);
vel.y += force * sin(ang);
}
noStroke();
fill(h, 255, 255);
ellipse(pos.x, pos.y, 5, 5);
}
}
Create an ArrayList of particles, but don't add any particle in setup():
ArrayList<Particle> paticles = new ArrayList<Particle>();
void setup() {
size(400, 400);
colorMode(HSB);
stroke(255);
}
Consecutively add the particles in draw(). The function millis() is used to get the time since the program was started:
void draw() {
int num = 100;
int interval = 100; // 0.5 seconds
int time = millis(); // milliseconds since starting the program
if (paticles.size() < num && paticles.size()*interval+5000 < time) {
paticles.add(new Particle(new PVector(random(width), random(height)), 100, 150));
}
background(0);
for (int i = 0; i < paticles.size(); i ++) {
Particle p = paticles.get(i);
p.update(paticles, i);
}
}
Note, the class Particle has to be adapted, because it has to operate with the ArrayList of variable length rather than the array with fixed length:
class Particle {
PVector pos;
PVector vel;
float r, mr;
float spd = 0.1;
float max = 2;
Particle(PVector pos, float r, float mr) {
this.pos = pos;
this.r = r;
this.mr = mr;
vel = new PVector(random(-1, 1), random(-1, 1));
}
void update(ArrayList<Particle> paticles, int i) {
float h = map(mouseX, 0, width, 0, 255);
pos.add(vel);
if (pos.x < -10) pos.x = width;
if (pos.x > width + 10) pos.x = 0;
if (pos.y < -10) pos.y = height;
if (pos.y > height + 10) pos.y = 0;
vel.x = constrain(vel.x + random(-spd, spd), -max, max);
vel.y = constrain(vel.y + random(-spd, spd), -max, max);
for (int j = i + 1; j < paticles.size(); j ++) {
Particle pj = paticles.get(j);
float ang = atan2(pos.y - pj.pos.y, pos.x - pj.pos.x);
float dist = pos.dist(pj.pos);
if (dist < r) {
stroke(h, 255, map(dist, 0, r, 255, 0));
strokeWeight(map(dist, 0, r, 3, 0));
line(pos.x, pos.y, pj.pos.x, pj.pos.y);
float force = map(dist, 0, r, 4, 0);
vel.x += force * cos(ang);
vel.y += force * sin(ang);
}
}
float ang = atan2(pos.y - mouseY, pos.x - mouseX);
float dist = pos.dist(new PVector(mouseX, mouseY));
if (dist < r) {
stroke(0, 0, map(dist, 0, r, 255, 0));
strokeWeight(map(dist, 0, r, 3, 0));
line(pos.x, pos.y, mouseX, mouseY);
float force = map(dist, 0, r, 30, 0);
vel.x += force * cos(ang);
vel.y += force * sin(ang);
}
noStroke();
fill(h, 255, 255);
ellipse(pos.x, pos.y, 5, 5);
}
}
I'm developing a game for a school project, a Bomberman-like game.
I'm using swing and I was using Canvas to draw my graphics but the KeyListener was not working, so I quitted using Canvas and started using paintComponent(Graphics g). The KeyListener is responding now, but my graphics don't refresh when my while loop calls the repaint() methods.
My code:
dispose();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
board b = new board();
b.setSize(630, 650);
b.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b.setVisible(true);
direction dessin = new direction();
b.add(dessin);
b.setVisible(true);
dessin.setBackground(Color.BLACK);
}
});
Then:
package Bm;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import javax.swing.JComponent;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class direction extends JPanel implements Runnable {
static float bmx = 35;
static float bmy = 35;
static float ex = 520;
static float ey = 520;
static float v = 0.03f;
static boolean gauche;
static boolean droite;
static boolean haut;
static boolean bas;
static void movEnnemi() {
int r = 1 + (int) (Math.random() * ((4 - 1) + 1));
Ennemi.droite = false;
Ennemi.gauche = false;
Ennemi.bas = false;
Ennemi.haut = false;
switch (r) {
case 1:
Ennemi.droite = true;
break;
case 2:
Ennemi.gauche = true;
break;
case 3:
Ennemi.bas = true;
break;
case 4:
Ennemi.haut = true;
break;
}
try {
Thread.sleep(5);
} catch (Exception e) {
}
;
}
public direction() {
super();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int ligne = 0; ligne < board.gridHauteur; ligne++) {
for (int colonne = 0; colonne < board.gridLargeur; colonne++) {
switch (board.plateau1[ligne][colonne]) {
case 0:
g.setColor(Color.lightGray);
g.fillRect(30 * ligne, 30 * colonne, 30, 30);
break;
case 1:
g.setColor(Color.black);
g.fillRect(30 * ligne, 30 * colonne, 30, 30);
board.plateau1[ligne][colonne] = board.BLOCKED;
break;
case 2:
g.setColor(Color.darkGray);
g.fillRect(30 * ligne, 30 * colonne, 30, 30);
board.plateau1[ligne][colonne] = board.BLOCKED;
break;
}
}
}
g.setColor(Color.blue);
g.fillRect((int) ex, (int) ey, 20, 20);
g.setColor(Color.red);
g.fillRect((int) bmx, (int) bmy, 21, 21);
g.dispose();
}
public void run() {
long dernierTempsLoop = System.currentTimeMillis();
while (true) {
long delta = (System.currentTimeMillis() - dernierTempsLoop);
dernierTempsLoop = System.currentTimeMillis();
movEnnemi();
for (int i = 0; i < delta / 5; i++) {
logic(5);
Ennemi.logic(5);
}
if ((delta % 5) != 0) {
logic(delta % 5);
Ennemi.logic(delta % 5);
}
System.out.println((int) (bmx / 30) + " - " + (int) (bmy / 30));
try {
Thread.sleep(20);
} catch (Exception e) {
}
;
repaint(); // <== HERE
}
}
public static void logic(long delta) {
float dx = 0;
float dy = 0;
if (gauche) {
dx--;
}
if (droite) {
dx++;
}
if (haut) {
dy--;
}
if (bas) {
dy++;
}
if ((dx != 0) || (dy != 0)) {
joueur.mouvement(dx * delta * v, dy * delta * v);
if (joueur.mouvement((dx * delta * v), (dy * delta * v)) == false) {
if (joueur.mouvement(0, dy * delta * v)) {
joueur.mouvement(0, dy * delta * v);
}
if (joueur.mouvement(dx * delta * v, 0)) {
joueur.mouvement(dx * delta * v, 0);
}
}
}
}
}
And :
package Bm;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Timer;
import javax.swing.*;
#SuppressWarnings("serial")
class board extends JFrame implements KeyListener {
static JPanel p;
public Timer fpstimer;
public direction g;
static final int BLOCKED = 1;
static int gridLargeur = 21;
static int gridHauteur = 21;
int fenLargeur = 630;
int fenHauteur = 650;
public static int plateau1[][] = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } };
public board() {
super("Bomberman");
g = new direction();
addKeyListener(this);
threadLoop p = new threadLoop("loop");
p.start();
}
public static boolean blocked(double d, double e) {
return plateau1[(int) d][(int) e] == BLOCKED;
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
direction.gauche = true;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
direction.droite = true;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
direction.bas = true;
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
direction.haut = true;
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
direction.gauche = false;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
direction.droite = false;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
direction.bas = false;
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
direction.haut = false;
}
}
public void keyTyped(KeyEvent e) {
}
}
class threadLoop extends Thread {
public direction g;
threadLoop(String name) {
super(name);
}
public void run() {
g = new direction();
g.run();
}
}
public class Jouer {
}
I hope you understand my problem, and will be able to help me, thank you :)
You are creating different instances of your class direction, hence the issue you are seeing
You should not use static like this. It goes against good OO-programming
Use Swing key-bindings instead of using KeyListener
Follow java naming conventions (your code is really hard to read for now): Classes start with an Upper case letter, methods and variables with a lower-case letter. Use CamelCase to concatenate words.
Don't extend when not needed (JFrame, Thread, etc...)
Try to separate the various concepts of your program (one part should be responsible for the display (displaying the board, the enemies, the player), another one to react on user input (left-key pressed, right-key pressed, etc...) and a third one to handle the logic of your game (player location, enemies location, the board, etc...)).
Here is a very lame attempt to explain those various advices:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class Game {
private static final String ICON_URL = "http://images2.wikia.nocookie.net/__cb20100515002803/fanon/images/a/a2/Bomberman_sprite.png";
private static final int GRID_SIZE = 24;
private static final int SQUARE_SIZE = 30;
private JFrame frame;
private Board board;
private static class Board extends JPanel {
private int[][] grid;
private int playerX;
private int playerY;
private ImageIcon playerIcon;
public Board() throws MalformedURLException {
// Some code to generate a random pseudo-board
Random random = new Random();
grid = new int[GRID_SIZE][];
for (int i = 0; i < GRID_SIZE; i++) {
grid[i] = new int[GRID_SIZE];
for (int j = 0; j < GRID_SIZE; j++) {
int r = random.nextInt(10);
grid[i][j] = r > 8 ? 2 : r > 6 ? 1 : 0;
}
}
playerIcon = new ImageIcon(new URL(ICON_URL));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(GRID_SIZE * SQUARE_SIZE, GRID_SIZE * SQUARE_SIZE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// pseudo-board painting
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
switch (grid[i][j]) {
case 1:
g.setColor(Color.GREEN);
g.fillRect(i * SQUARE_SIZE, j * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
break;
case 2:
g.setColor(Color.RED);
g.fillRect(i * SQUARE_SIZE, j * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
break;
default:
break;
}
}
}
// Player painting
int x = playerX * SQUARE_SIZE + (SQUARE_SIZE - playerIcon.getIconWidth()) / 2;
int y = playerY * SQUARE_SIZE + (SQUARE_SIZE - playerIcon.getIconHeight()) / 2;
g.drawImage(playerIcon.getImage(), x, y, this);
}
public int getPlayerX() {
return playerX;
}
public int getPlayerY() {
return playerY;
}
public void setPlayerX(int playerX) {
if (playerX >= 0 && playerX < GRID_SIZE && grid[playerX][playerY] == 0) {
this.playerX = playerX;
repaint();
}
}
public void setPlayerY(int playerY) {
if (playerY >= 0 && playerY < GRID_SIZE && grid[playerX][playerY] == 0) {
this.playerY = playerY;
repaint();
}
}
}
private class MoveLeftAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
board.setPlayerX(board.getPlayerX() - 1);
}
}
private class MoveRightAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
board.setPlayerX(board.getPlayerX() + 1);
}
}
private class MoveUpAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
board.setPlayerY(board.getPlayerY() - 1);
}
}
private class MoveDownAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
board.setPlayerY(board.getPlayerY() + 1);
}
}
private class ExitAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
int i = JOptionPane.showConfirmDialog(board, "Are you sure you want to exit?");
if (i == JOptionPane.YES_OPTION) {
System.exit(0);
}
}
}
protected void initUI() throws MalformedURLException {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
board = new Board();
board.setBackground(Color.BLACK);
board.registerKeyboardAction(new MoveLeftAction(), KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_FOCUSED);
board.registerKeyboardAction(new MoveRightAction(), KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_FOCUSED);
board.registerKeyboardAction(new MoveUpAction(), KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_FOCUSED);
board.registerKeyboardAction(new MoveDownAction(), KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);
board.registerKeyboardAction(new ExitAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
frame.add(board);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
new Game().initUI();
} catch (MalformedURLException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Could not load icon from Internet", "Unable to start", JOptionPane.ERROR_MESSAGE);
}
}
});
}
}
Please note that the display and the logic of the games are here completely intertwined, so I don't quite follow advice nr 6.
The direction you assign to the frame/board is not the same you a trying to paint to. You create a new reference in your main loop
I'd also suggest you use key bindings over key listeners
Updated
You construct a initial direction and then add that to the screen. This is actually what will be rendered.
direction dessin = new direction();
b.add(dessin);
But in your threadLoop you create a new (disconnected) direction and start trying to update it...
public void run() {
g = new direction();
g.run();
}
...this will never paint as it has no connection to the screen.
You also create a third reference in board...
g = new direction();
All these disconnected direction classes have no one to paint or communicate with each and are unnecessary.
I would create a single reference in board, add it to the frame and pass that reference to the threadLoop
Updated
You could take a look at this. It's a basic example demonstrating a simple threaded animation engine and key bindings for the movement of a game asset