I am making a game in Android Studio. Now my game is complete, but game speed is different on large displays...
I run my game with this timer:
if(timer == null){
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if (start_flg) {
handler.post(new Runnable() {
#Override
public void run() {
changePos();
}
});
}
}
}, 0, 20);
}
changePos() looks like this:
public void changePos() {
SPEED_BOX =(screenHeight/280);
long time = System.nanoTime();
double delta_time = (double) ((time - last_time) / 1000000)/10;
last_time = time;
// Move Box
if (action_flg) {
// Touching
boxY += SPEED_BOX*delta_time;
box.setImageDrawable(imageBox1);
} else {
// Releasing
boxY -= SPEED_BOX*delta_time;
box.setImageDrawable(imageBox2);
}
// Check box position.
if (boxY < 0) {
boxY = 0;
box.setImageDrawable(imageBox1);
}
if (frameWidth - boxSize < boxY) {
boxY = frameWidth - boxSize;
box.setImageDrawable(imageBox2);
}
box.setY(boxY);
}
id correct that my deltaTime is allways between 1.5 to 2.9?
but every time I try it in different ways always game speed is not correct.
Is it possible to make my game running same speed on different devices, different screen sizes?
The problem was that screenHeight was screens height in pixels but the game does not use the whole screen. This caused that the speed was different on different devices. So screenHeight should be changed to gamesLayout.getHeight().
Related
hi guys as title,
I want to add multiple images(red fan) that are continuously rotated on an existing image (about 10~40),I have implemented it by using View "onDraw", but the system resource consumption is very serious, the CPU usage is 30%~40%.
Is there any better way? Like using a game framework or surfaceview?
Thanks in advance.
onDraw
#Override
protected void onDraw(Canvas canvas) {
int j=1;
final int viewHeight =610;
for (final Fan fan : mFans) {
j = j+1;
final float fanSize = 8;
// // Save the current canvas state
final int save = canvas.save();
if(XArray.size()>0){
y = (int) YArray.get(j);
x = (int) XArray.get(j);
// Move the canvas to the center of the fan
canvas.translate(x, y);
// Rotate the canvas based on how far the fan has moved
final float progress = (fan.y + fanSize) / viewHeight;
canvas.rotate(360 * progress);
// Prepare the size and alpha of the drawable
final int size = (int) fanSize;
mDrawable.setBounds(-size, -size, size, size);
// Draw the fan to the canvas
mDrawable.draw(canvas);
// Restore the canvas to it's previous position and rotation
canvas.restoreToCount(save);
}}
}
onAttachedToWindow()
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mTimeAnimator = new TimeAnimator();
mTimeAnimator.setTimeListener(new TimeAnimator.TimeListener() {
#Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
if (!isLaidOut()) {
// Ignore all calls before the view has been measured and laid out.
return;
}
updateState(deltaTime);
invalidate();
}
});
mTimeAnimator.start();
}
/**
* Pause the animation if it's running
*/
public void pause() {
if (mTimeAnimator != null && mTimeAnimator.isRunning()) {
// Store the current play time for later.
mCurrentPlayTime = mTimeAnimator.getCurrentPlayTime();
mTimeAnimator.pause();
}
}
/**
* Resume the animation if not already running
*/
public void resume() {
if (mTimeAnimator != null && mTimeAnimator.isPaused()) {
mTimeAnimator.start();
// Why set the current play time?
// TimeAnimator uses timestamps internally to determine the delta given
// in the TimeListener. When resumed, the next delta received will the whole
// pause duration, which might cause a huge jank in the animation.
// By setting the current play time, it will pick of where it left off.
mTimeAnimator.setCurrentPlayTime(mCurrentPlayTime);
}
}
/**
* Progress the animation by moving the Fans based on the elapsed time
* #param deltaMs time delta since the last frame, in millis
*/
private void updateState(final float deltaMs) {
// Converting to seconds since PX/S constants are easier to understand
new Thread(new Runnable(){
#Override
public void run() {
final float deltaSeconds = deltaMs / 1000f;
final int viewWidth = getWidth();
final int viewHeight = getHeight();
for (final Fan fan : mFans) {
// Move the Fan based on the elapsed time and it's speed
fan.y -= fan.speed * deltaSeconds;
// If the Fan is completely outside of the view bounds after
// updating it's position, recycle it.
final float size = fan.scale * mBaseSize;
// if (fan.y + size < 0) {
// initializeFan(fan, viewWidth, viewHeight);
// }
} // long run job
}
}).start();
}
So I'm making a game that connects to a server where all the calculations of x, y etc will be done using elapsed time. On the client where the graphics are displayed I'm currently using the paintcomponent() and repaint() to draw the graphics. Is it ok to use this method? Because on different computers the speed of a character moving will be different based on how fast that computer can run the paintcomponent(). Let's say I'm getting 200fps while running the game.. does that mean paintcomponent is being called 200 times a second? and for slower performing computers would the paintcomponent method be called ie 60 times a second if 60fps?
(I'm using a getframerate method to find the framrate)
GOAL: My goal is to make the game run the same on all machines, not varying based on how fast the computer is
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.clearRect(0, 0, WIDTH, HEIGHT);
if (gameState == 0) { // main menu
menuScreen.draw(g); // draw main menu screen
} else if (gameState == 1) { // instructions
instrScreen.draw(g);
} else if (gameState == 2) { // options
optionsScreen.draw(g); // draw options screen
} else if (gameState == 3) { // game
man.draw(g); // test
if (upKey) {
man.changeDirection("UP");
man.move();
//outputToServer("W");
}
if (downKey) {
man.changeDirection("DOWN");
man.move();
//outputToServer("S");
}
if (leftKey) {
man.changeDirection("LEFT");
man.move();
//outputToServer("A");
}
if (rightKey) {
man.changeDirection("RIGHT");
man.move();
//outputToServer("D");
}
}
g.setColor(Color.BLACK);
g.drawString(getFrameRate(), 10, 15);
repaint();
}
private String getFrameRate() {
long currentTime = System.currentTimeMillis();
deltaTime += currentTime - lastTimeCheck;
lastTimeCheck = currentTime;
frameCount++;
if (deltaTime >= 1000) {
frameRate = frameCount + "fps";
frameCount = 0;
deltaTime = 0;
}
return frameRate;
}
I know there are a lot of problems but perhaps by freezing another task to achieve the nearest result possible or by using a parallel thread?
Here is my code:
public void draw(Canvas canvas) {
DrawButtons(canvas);
DrawPercise(canvas);
DrawLines(canvas);
}
private void DrawButtons(Canvas canvas) {
canvas.drawBitmap(Button, 50, 0, null);
}
private void DrawPercise(Canvas canvas) {
if (System.nanoTime() >= AllowedTimeinNano) {
// Save time again for Next if
//if 50000000 nanoseconds passed do it again
AllowedTimeinNano = (long) (System.nanoTime() + (20000000000f / 400));
DoTask();
}
}
private void DrawLines(Canvas canvas) {
for (float i = 40; i < 800; i += 40) {
canvas.drawLine(0, i, 800, i, TablePaint);
}
}
The problem is if my task takes too long or the target device has bad performance, then the timing becomes incorrect, and the whole point of the app is based on this timing. I know this may be impossible but could you give some tips?
I thought I would answer this question to clairify what I said in the comment.
Thread:
public class GameThread extends Thread {
private int FPS = 60;
private double averageFPS;
private SurfaceHolder surfaceHolder;
private Clicker gamePanel;
private boolean running;
public static Canvas canvas;
public GameThread(SurfaceHolder surfaceHolder, Clicker gamePanel)
{
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run()
{
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount =0;
long targetTime = 1000/FPS;
while(running) {
startTime = System.nanoTime();
canvas = null;
//try locking the canvas for pixel editing
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.gamePanel.tick();
this.gamePanel.draw(canvas);
}
} catch (Exception e) {
}
finally{
if(canvas!=null)
{
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}
catch(Exception e){e.printStackTrace();}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime-timeMillis;
try{
sleep(waitTime);
}catch(Exception e){}
totalTime += System.nanoTime()-startTime;
frameCount++;
if(frameCount == FPS)
{
averageFPS = 1000/((totalTime/frameCount)/1000000);
frameCount =0;
totalTime = 0;
System.out.println(averageFPS);
}
}
}
public void setRunning(boolean b)
{
running=b;
}
}
I got it when I took a tutorial when I started up, and I have only used this. Before you can use it there are some things you have to do:
Have a class that extends SurfaceView implements SurfaceHolder.Callback
Replace 'Clicker' with the name of your class.
For FPS calculation:
milliseconds in 1 second / FPS = how often in milliseconds it will update
1000 / 60 = 16.666666
Which means it updates every 0.01666666 seconds
How do I use it?
Simple. (You have to find the propper places to place them yourself)
Creating it:
if(gt == null) {
gt = new GameThread(getHolder(), this);
gt.setRunning(true);
gt.start();
}
Stopping it:
if(gt != null) {
try {
gt.setRunning(false);
gt.join();
gt = null;
} catch (InterruptedException e) {
Toast.makeText(c, "An error occured when stopping the thread.", Toast.LENGTH_LONG)
.show();
}
}
In my experience, 60 FPS is the best FPS to take to secure that this will work on all devices. All though, there are some exceptions.
Normal phones today have 2GB of ram or more. For an instance, S7 has 4 gigabytes of ram. However, if there is 512 MB of ram, I'm not sure as of performance. But there are very few devices left in the world with 512 MB RAM. There are some budget phones, but there are not a lot of those with only 512 MB of RAM unless you would select the really old versions.
So, by using the thread above, you will have a game thread that updates contantly and will never stop. You do not have to worry performance wise, because there are mostly new devices running for real potential customers.
Additionally, this thread works in a very simple way:
FPS is the max FPS and will therefore not go higher, so lower end devices will go for as high as possible up to 60, while newer will stay steady at 60.
All though I have experienced with my phone that it sometimes go up to 62, but it isn't really a problem because it is only two FPS too much.
Remember:
The more actions that happen in methods touched by the gamethread, the more strain it is on the system and on the app. Any game can reach 2000 fps if there is no limit and nothing happens. While loops are fast!
I have a marching sound file which I play once a specific event occurs, but the issue comes down to having the file fade out in a specified amount of time before being stopped when the volume reaches 0. This project is for Desktop.
Sound marching = Gdx.audio.newSound(Gdx.files.internal("sounds/test.wav"));
The first attempt I had at this was scheduling a task through the Timer:
final long mId = marching.play(1f, 1f, 0);
for (float i = 1; i > 0; i -= 0.01) {
Timer.schedule(new Task() {
#Override
public void run() {
marching.setVolume(mId, i);
}
}, 0.3f);
}
marching.stop();
This, however seemed to invoke marching#stop before marching#setVolume
I decided to take another apporach:
float volume = 1;
final long mId = marching.play(volume, 1f, 0), startTime = TimeUtils .millis();
while (volume > 0) {
if (TimeUtils.millis() - startTime % 300 == 0) marching.setVolume(mId, volume -= 0.1);
}
marching.stop();
I thought that this would reduce the volume based on the amount of time allotted, yet it only made the program freeze.
Is there a straightforward way of fading SFX audio out or some manner of setting frequent sleeps so that I can lower the volume iteratively?
Thanks.
I think the easiest way is to put this in the render method:
float FACTOR = 10; // The bigger the factor, the faster the fade-out will be
float mVolume = 1;
boolean mIsPlaying = true;
public void render(float delta) {
// ...
if (mIsPlaying) {
mVolume -= delta * FACTOR;
if (mVolume > 0) {
marching.setVolume(mId, mVolume);
}
else {
marching.stop(mId);
mIsPlaying = false;
}
}
// ...
}
First solution is wrong, I rewrote it for you:
final long mId = marching.play(1f, 1f, 0);
Timer.schedule(new Task() {
#Override
public void run() {
for (float i = 1; i > 0; i -= 0.01)
{
marching.setVolume(mId, i);
if(i<=0)
marching.stop();
}
}
}, 0.3f);
Second solution freeze your app because you are calling it in the main thread and not in a async task!
However using the render task is not a good idea. You should detach render task from game loop task. Read that: http://gameprogrammingpatterns.com/game-loop.html. Logic of your game should run at the same speed in all machine. Cannot be limited by rendering issue/slow problem.
I find this works much better than using a loop.
I set MUSIC_FADE_STEP to 0.01f (i.e adjust volume by 1% each time through).
I adjust fadeRate (in seconds) as required. 0.01f works well as a starting point.
Timer.schedule(new Timer.Task() {
#Override
public void run() {
if (music.getVolume() >= MUSIC_FADE_STEP)
music.setVolume(music.getVolume()-MUSIC_FADE_STEP);
else {
music.stop();
this.cancel();
}
}
}, 0f, fadeRate);
I have a question. I have a timer that looks like a progress bar moving backwards. Now it works good or I should say well on older phones but the newer one with the HD screens well it doesn't work well. The timer bar is set vert and match_parent in my xml.
I am including my method below can you look at it and tell me how i can improve it?
//used to animate timer bar
private Handler mHandler = new Handler();
//timer event code
private Runnable mUpdateTimeTask = new Runnable() {
public void run() {
final long start = startTime;
// startTime will come from a spinner it will be 0,1,2,3,4 or 5 (users choice)
// ** the bar is GREEN until the if condition is meet
long currentLength = System.currentTimeMillis() - start;
long remainingTime = gameTime - currentLength;
float x = (float)remainingTime / (float)gameTime;
if(remainingTime > 0)
{
//still have time remaining - update UI
LinearLayout timerUI = (LinearLayout) findViewById(R.id.timerUI);
timerUI.getLayoutParams().height = (int) (x * 400);
//update color
if(remainingTime < 15000)
timerUI.setBackgroundColor(Color.RED);
else if(remainingTime < 30000)
timerUI.setBackgroundColor(Color.YELLOW);
timerUI.requestLayout();
timerUI.invalidate();
mHandler.postDelayed(this, 500);
}
else
{
if(monitorThread != null)
monitorThread.interrupt();
//NEED to push user to final screen
Intent resultsIntent = new Intent(SingleGameActivity.this,
ResultsActivity.class);
startActivity(resultsIntent);
}
}
};