So I was told to create a simple game in android using canvas only for a school project (meaning I can't use any extensions or game engines beside the ones that come with android), and I wanted to create a simple 2d game with a player walking around.
I did it like that:
public GameView(Context c) {
// TODO Auto-generated constructor stub
super(c);
this.c=c;
this.Sprite=BitmapFactory.decodeResource(getResources(), R.drawable.walk1);
this.Sprite=Bitmap.createScaledBitmap(Sprite, Sprite.getWidth()*2, Sprite.getHeight()*2, false);
sprite2=new Sprite("Spicy",Sprite);
this.requestFocus();
this.setFocusableInTouchMode(true);
animate1();
}
I created a view class, loaded a simple player sprite, created sprite class and an handler -
public void animate1(){
handlerAnimation100 = new Handler();
final Runnable r = new Runnable() {
public void run() {
invalidate();
handlerAnimation100.postDelayed(this, 1);
}
};
handlerAnimation100.postDelayed(r, 1);
}
that does invalidate every 0.001 seconds for the game time and animation.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
sprite2.Draw(canvas);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
sprite2.Update(keyCode);
invalidate();
return false;
}
on onDraw i called the draw function in the sprite class and onkeydown I sent the key pressed to the update function in the sprite class.
Now for the sprite class there isn't something special:
public class Sprite {
enum State
{
Walking
}
State mCurrentState = State.Walking;
int mDirection = 0;
int mSpeed = 0;
int mPreviousKeyboardState;
private String spriteName;
Bitmap sprite;
int SPRITE_SPEED = 5;
int MOVE_LEFT = -1;
int MOVE_RIGHT = 1;
private float mScale = 1.0f;
Point Position;
public Sprite(String name,Bitmap sprite) {
this.sprite=sprite;
this.spriteName=name;
Position=new Point(150,150);
}
public void Update(int keyboard)
{
int aCurrentKeyboardState = keyboard;
UpdateMovement(aCurrentKeyboardState);
mPreviousKeyboardState = aCurrentKeyboardState;
}
private void UpdateMovement(int aCurrentKeyboardState)
{
if (mCurrentState == State.Walking)
{
mSpeed = 0;
mDirection = 0;
if (aCurrentKeyboardState==KeyEvent.KEYCODE_A)
{
mSpeed = SPRITE_SPEED;
mDirection = MOVE_LEFT;
}
else if(aCurrentKeyboardState==KeyEvent.KEYCODE_D)
{
mSpeed = SPRITE_SPEED;
mDirection= MOVE_RIGHT;
}
Position.x += mDirection * mSpeed;
}
}
public void Draw(Canvas c)
{
c.drawBitmap(sprite, Position.x,Position.y, null);
}
}
I just change the position of the image and move it depending on the key pressed.
Now here is the problem:
Using handler and invalidate was the only option I was able to find to replace "Game time" that appears in game engines, and although it works, it works very choppily, if the speed of the player is high it looks like it jumps pixels and if its low the animation is blurry and very slow, it looks like the invalidate takes more time and it happens not every 0.001 seconds but 0.5 seconds or so.
Here is how it looks like:
Slow speed (blurry and very slow):
Faster Speed (choppy not smooth):
Is there a better way to do it, again with only using what android offers?
Threads! However the handler is not the root of your problem judging by how slow your animation is running and how choppy the fast one is, you are probably using a canvas tied into an ImageView? This is not very fast at all. You should look into using a SurfaceView (works with lower APIs but not the fastest) or into TextureView (super fast, I had to delay my thread cause the animation was just a blur). Both of these rely on canvas at their core.
There are lots of examples out there on in internets on how to code these and you can adapt them for your purposes. To give you a place to start you can look HERE at some samples I wrote.
Related
I am working on a 2d game using Swing. Before, I used to render my objects and player on a jPanel over the
panel.repaint();
method and would override the paint methode in the panel class. Then I learned about the concept of moving the render code to a Render class which looks like this:
public class Renderer{
public void render(Graphics g, Game game){
game.getObjects.forEach(gameObject -> g.drawImage(....);
}
}
With that code there is always a drawing on a drawing, ....
The problem with this is that I can't (or don't know how to) call the super method repaint() of the panel.
I would like to keep the Render class because the code is much more structured. Any advice on how to reset a jPanel?
I tried using panel.repaint() before calling the render method but I just got a blank screen.
public class Renderer {
public void render(Game game, Graphics graphics) {
Player player = game.getPlayer();
graphics.drawImage(player.getImage(), (int)player.getPosition().getX(), (int)player.getPosition().getY(), null);
}
}
public class Game{
private static Game instance;
private GamePanel gamePanel;
private Player player;
private Renderer renderer;
private boolean isRunning = true;
private final int MAX_FPS = 60;
private Game() {
initialize();
startGameLoop();
}
private void initialize() {
renderer = new Renderer();
player = Player.getInstance();
gamePanel = GamePanel.getInstance(this);
GameWindow.getInstance(gamePanel);
}
private void startGameLoop() {
double timePerCycle = 1_000_000_000 / MAX_FPS;
int updates = 0;
long lastInfo = System.currentTimeMillis();
long timeBefore = System.nanoTime();
while(isRunning) {
if(System.nanoTime() - timeBefore >= timePerCycle) {
timeBefore = System.nanoTime();
update();
render();
updates++;
}
if(System.currentTimeMillis() - lastInfo >= 1000) {
System.out.printf("UPS: %d\n", (updates / (( System.currentTimeMillis() - lastInfo) / 1000)));
lastInfo = System.currentTimeMillis();
updates = 0;
}
}
}
private void render() {
Graphics graphics = gamePanel.getGraphics();
renderer.render(this, graphics);
graphics.dispose();
}
To clear the panel you can employ a boolean in paintComponent and fill in the rectangle via g.fillRect(x,y, width, height).
Her is one possible example. Where boolean clearScreen is an instance field.
public void clear() {
clearScreen = true; // tested in paintComponent
repaint();
clearScreen = false;
}
Here are some other suggestions.
don't override paint for JPanel. Use paintComponent.
first statement should be super.paintComponent(g). This is what allows panel.setBackground() to work, among other things as it calls the overridden method to perform additional functionality.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// your stuff here
}
Use a Swing Timer for controlling repaint cycles.
painting and event handling are done in the EventDispatch thread. So processing should be kept to a minimum. Any computations required for your game should be done outside that thread and when possible, only the actual invocation of the graphics methods should be done in the paintComponent method.
If done properly, subsequent calls to repaint() will not add to what is there. Each call must redraw everything including changes.
For more information check out How to paint
There are many examples of painting on this site. Search for them using [Swing] and [Graphics] tags. Here is one that employs some of the above. Also note that Swing components enable double buffering by default
So I've been trying to figure out what's wrong with my code, but I've had basically no luck when it comes to finding what's wrong. I've seen plenty of folks who had similar issues but none of the fixes that were suggested helped with my problem.
I've made a video showcasing my problem, but I'll give an explanation here too: whenever I move on the screen, the tiles in my game engine don't sync up with each other in terms of positioning, and it seems to be only on the rendering end. When I do any sort of checks to see if the distance between two tiles changes, I don't get any sort of errors. I get these gaps between the tiles specifically when I'm moving up or left, and they instead merge slightly when I move down or right, as if some of the tiles are updating faster than the others.
I don't want to just start dumping my couple thousand lines of code here since that won't really help anyone, but I'm also not well versed in the game development world, so I'm not entirely sure what pieces of code are super relevant here. If there's anything that y'all would like to see, let me know and I'll happily provide it.
The main method and postInit looks like this:
public static void main (String[] args)
{
EventQueue.invokeLater(() ->{
ex = new ShootyGame();
ex.dispose();
ex.setLayout(null);
ex.setUndecorated(Settings.isFullscreen);
ex.setVisible(true);
ex.setCursor(ex.getToolkit().createCustomCursor(
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), new Point(), null)); //hides the system cursor
ex.createBufferStrategy(2);
postInit();
});
}
private static void postInit()
{
//add a window listener to the main JFrame
ex.addWindowListener(new WindowAdapter()
{
/**
* Autosaves when the window is closed, then kills the game.
*/
#Override
public void windowClosing(WindowEvent e)
{
//TODO autosaving
kill();
}
});
render.start(); //these are just the two threads being started
run.start();
}
My two threads, which basically just run the step and render methods in my game class:
private static class Running extends Thread
{
boolean stop = false;
int fps = TickManager.fromFPS(Settings.FPS);
public void run()
{
while (!stop)
{
try
{
Running.sleep(fps);
}
catch(InterruptedException e)
{
System.err.println("B");
}
game.step();
}
}
}
private static class Render extends Thread
{
boolean stop = false;
int fps = TickManager.fromFPS(Settings.FPS);
public void run()
{
while (!stop)
{
try
{
Running.sleep(fps);
}
catch(InterruptedException e)
{
System.err.println("A");
}
game.render();
}
}
}
The run method in my Game class just calls the current character's step method (it'll call more later), which is what's directly used to move the Camera class' x and y position. The character step looks like this:
public void step()
{
this.calculateMove(); //gets info on which keys are being pressed, adds or subtracts an integer from dx and dy (which are also ints) based on which are being pressed
Game.cam.update(-this.dx, -this.dy); //sends the opposite movement to the camera
//change the position of the player character by the appropriate x and y amounts
this.changeX(this.dx);
this.changeY(this.dy);
this.reset(); //just sets dx and dy back to 0 after moving
}
And now to the rendering stuff here's the entire camera class since it's probably the most relevant class:
public class Camera
{
private int x;
private int y;
public Camera(int x, int y)
{
this.x = x;
this.y = y;
}
public void update(int dx, int dy)
{
moveX(dx);
moveY(dy);
}
public void moveX(int dx)
{
this.x += dx;
}
public void moveY(int dy)
{
this.y += dy;
}
/**
* Gets the top left corner of the camera's x position.
* #return The x position of the top left corner of the camera
*/
public int getCamX()
{
return this.x;
}
/**
* Gets the top left corner of the camera's y position.
* #return The y position of the top left corner of the camera
*/
public int getCamY()
{
return this.y;
}
}
And finally, the relevant code for the actual painting of the tiles:
public void render() //this is the part that the render thread calls initially
{
Toolkit.getDefaultToolkit().sync();
repaint();
}
public void paint(Graphics g) //the paint method, mostly handled in doDrawing
{
super.paint(g);
doDrawing(g);
}
private void doDrawing(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
//stuff that happens if the game isn't paused
if(!GameStates.isPaused)
{
currentMap.drawMap(g2d, this);
currentChar.draw(g2d, this);
}
}
public void drawMap(Graphics2D g2d, ImageObserver observer)
{
//for each tile in the map, call its draw method
for(int i = 0; i < this.tileArray.length; i++)
{
for(int j = 0; j < this.tileArray[0].length; j++)
{
this.tileArray[i][j].draw(g2d, observer);
}
}
}
public void draw(Graphics2D g2d, ImageObserver observer, BufferedImage image)
{
g2d.drawImage(image, this.getX() + Game.cam.getCamX(), this.getY() + Game.cam.getCamY() observer);
}
Sorry for the long winded post, I've just tried everything I can think of (and that my Googling abilities can help me think of) and I'm throwing this out as a last-ditch effort before I just move to an actual game engine. I wanted to make my own engine from scratch for the sake of getting more experience in Java, but if I can't figure this out I'd rather just move to a proper engine and actually make a game. Any help is massively appreciated :)
EDIT: capitalization
I'm working on a 2D game on Android / Java.
During each frame, I use a fixed size Back Buffer bitmap (1024x768) to draw all the game's assets (background, sprites, ...).
Then at the end of the onDraw(), I draw this back buffer on the screen with the right size :
Rect Source = new Rect(0, 0, 1024, 768);
Rect Dest = new Rect(0, 0, m_ScreenWidth, m_ScreenHeight);
canvas.drawBitmap(BackBufferBitmap, Source, Dest, null);
The problem is that when I use this engine to just draw a simple 1024x768 image on the screen (the simplest I can do), the operation takes between 35 and 40 milliseconds on a LG G4 phone (i.e. approx 25fps). Is it possible to get a better fps with another way of managing 2D graphics?
I turn on hardware acceleration, thread's max priority.
I don't have this problem on phones with lower pixel count. I guess my problem is linked to the high number of pixels on a LG G4 (2560x1440).
Is it possible to do the drawing faster?
Or, otherwise, is it possible to just run my game on such high-definition devices with a lower definition (like we do on PC)?
EDIT : here is the full code :
1) my View
public class ElementaryView extends SurfaceView implements SurfaceHolder.Callback
{
private ElementaryThread m_Thread;
private SurfaceHolder m_Holder;
private Bitmap m_SimpleBitmap=null;
private Bitmap m_BackBuffer =null;
private Canvas m_BackBufferCanvas =null;
public ElementaryView(Context context)
{
super (context);
m_Holder =getHolder();
m_Holder.addCallback(this);
setFocusable(true);
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
m_Thread =new ElementaryThread(m_Holder,this);
m_Thread.setPriority(Thread.MAX_PRIORITY);
m_BackBuffer =Bitmap.createBitmap(1024,768,Config.ARGB_8888);
m_BackBufferCanvas =new Canvas(m_BackBuffer);
m_SimpleBitmap =BitmapFactory.decodeResource(this.getResources(), R.drawable.splashscreen);
//"splashscreen" is a 1024x768 jpg image
m_Thread.setRunning(true);
m_Thread.start();
}
#Override
protected void onDraw(Canvas canvas)
{
m_BackBufferCanvas.drawBitmap(m_SimpleBitmap, 0, 0, null);
Rect Source =new Rect(0,0,1024,768);
Rect Dest =new Rect(0,0,this.getWidth(),this.getHeight());
canvas.drawBitmap(m_BackBuffer,Source,Dest,null);
}
}
2) my thread :
public class ElementaryThread extends Thread
{
private SurfaceHolder m_SurfaceHolder;
private ElementaryView m_View;
private boolean m_Running;
public ElementaryThread(SurfaceHolder sh, ElementaryView view)
{
super();
m_SurfaceHolder = sh;
m_View = view;
}
public void setRunning(boolean r) { m_Running = r; }
#SuppressLint("WrongCall") #Override
public void run()
{
Canvas canvas;
while (m_Running)
{
canvas =null;
try
{
canvas =this.m_SurfaceHolder.lockCanvas();
this.m_View.onDraw(canvas);
}
finally
{
if (canvas!=null) m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
And this gives a frame rate lower than 30fps on a LG G4. Yet, a lot of 2D games run with a better fps on my G4. Does anybody knows how they do that ?
By default, the LG G4 will always run at a res. of 2560x1440, which of course is going to make it difficult for the phone to render any graphics. This is also why the lesser res. phones will run much more smoothly. So, the only solution to this problem would be to change the resolution dependent on the number of pixels and processing power of the device.
First, for this (for games) is better to use SurfaceView
Second, show the method onDraw entirely. Most likely, you have a resource-intensive operation there. (scale etc.)
I have onDraw takes about 3ms with a similar task.
I'm not sure that you are properly working. What do you want? "this.m_View.onDraw(canvas);"
Remove
#Override
protected void onDraw(Canvas canvas)
From ElementaryView
And move code into Thread. Something like this:
#SuppressLint("WrongCall") #Override
public void run()
{
Canvas canvas;
while (m_Running)
{
canvas =null;
try
{
canvas =this.m_SurfaceHolder.lockCanvas();
Rect Source =new Rect(0,0,1024,768);
Rect Dest =new Rect(0,0,m_View.getWidth(),m_View.getHeight());
canvas.drawBitmap(m_SimpleBitmap, 0, 0, null);
canvas.drawBitmap(m_BackBuffer,Source,Dest,null);
}
finally
{
if (canvas!=null) m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
I have a problem, I think its related to Screen render and its lifecycle.
Basically I have two screens (Menu and Game). In GameScreen render method i call World.update and after that my Render. In hide method (of GameScreen) i dispose of the SpriteBatch from Redner class.
So when I change the screen from Game to Menu (within World.update) Java crashes. As far as I can tell, the dispose is making the crash.
So my question is, when i set a new screen in the middle of the render cycle, is that render cycle still going to finish with its old screen? Meaning, am I calling batch.dispose before the rendering was finished, and that is why i get the problem?
Thank you for all the help
public class GameScreen extends AbstractGameScreen {
private static final String TAG = GameScreen.class.getName();
private WorldController worldController;
private WorldRenderer worldRenderer;
private boolean paused;
public GameScreen(Game game) {
super(game);
}
#Override
public void render(float deltaTime) {
// Do not update game world when paused
if (!paused) {
// Update game world by the time that has passed since last render time
worldController.update(deltaTime);
}
// Sets the clear screen color to: Cornflower Blue
Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f, 0xed / 255.0f, 0xff / 255.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Render game world to screen
worldRenderer.render();
}
#Override
public void resize(int width, int height) {
worldRenderer.resize(width, height);
}
#Override
public void show() { // Similar as create method
worldController = new WorldController(game);
worldRenderer = new WorldRenderer(worldController);
Gdx.input.setCatchBackKey(true);
}
#Override
public void hide() { // Similar to dispose method
worldRenderer.dispose();
Gdx.input.setCatchBackKey(false);
}
#Override
public void pause() {
paused = true;
}
#Override
public void resume() {
super.resume();
// Only called on Android
paused = false;
}
}
That's basically correct. The screen that calls setScreen from within its render method will have hide called on itself and then will continue through the rest of its code in its render method. So you are killing your sprite batch right before trying to draw with it.
So don't call dispose from within your hide method. In fact, it is probably bad practice for a Screen to ever call dispose on itself. You can reserve that for the Game class that owns it. For example, you could do something like this in your game class:
#Override
public void render() {
super.render();
if (getScreen() != gameScreen && gameScreen != null) {
gameScreen.dispose();
gameScreen = null;
}
}
By the way, you should probably put the SpriteBatch in your Game subclass and let all the different screens share it. It's a fairly big object to be allocating and deallocating for no reason.
when i touch finger on sprite, score increses randomly not by fix rate i mean that i set score
to increase 50 each time but when i scroll on sprite sometimes it increases by 100 sometimes by 150 and 200;
mHardware1[active] = new Sprite(pX, pY, samsung,
this.getVertexBufferObjectManager()) {
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
float X, float Y) {
if (pSceneTouchEvent.isActionMove()) {
scene.detachChild(mHardware1[active]);
score+50;
}
it works when i use isActionDown in place of isActionMove but m working a game like fruit ninja in which i need to scroll finger on screen that why i cant use isActionDown
You can use a SurfaceScrollDetector, which detects when the user is sliding his finger across the screen. It has the following events associated with it:
onScrollStarted
onScroll
onScrollFinished
I guess you could increase by 50 when you reach the onScrollFinished.
I am currently using the SurfaceScrollDetector for a project, but I haven't used it in a way you are asking about, so I can't say forsure if it will work as expected.
Here is one of the examples that uses it (in addition to the PinchZoomDetector):
https://github.com/nicolasgramlich/AndEngineExamples/blob/GLES2/src/org/andengine/examples/PinchZoomExample.java
Scene updates happen on a different thread to UI events, so possibly multiple queued touchscreen events are getting through before the event handler realises that the sprite has been detached.
Set a local boolean variable to prevent this, e.g.:
boolean touchProcessed = false;
mHardware1[active] = new Sprite(pX, pY, samsung,
this.getVertexBufferObjectManager());
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
float X, float Y) {
if (!touchProcessed & pSceneTouchEvent.isActionMove()) {
touchProcessed = true;
scene.detachChild(mHardware1[active]);
score += 50;
}
}
Note that you can use: mHardware1[active].detachSelf() instead of scene.detachChild(...).
Also note that you should be detaching in the sprite in the update thread, thus:
boolean touchProcessed = false;
mHardware1[active] = new Sprite(pX, pY, samsung,
this.getVertexBufferObjectManager());
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
float X, float Y) {
if (!touchProcessed & pSceneTouchEvent.isActionMove()) {
touchProcessed = true;
score += 50;
engine.runOnUpdateThread(new Runnable() {
#Override
public void run() {
mHardware1[active].detachSelf();
}
});
}
}