Need help on small-tower defense game (exercise in jmonkey ebook)

Before starting, I have to say that: I’m not good at english much (just enough to describe my idea), so if I make any mistake in spelling or English grammar, just ignore it ;)). I’m not living in a country that English is a first language (I’m living in VietNam, is there anyone here live in VietNam? :slight_smile: )
I’ve just learned jmonkey in few days, and this is the results of my try. The exercise in page 84, make a tower defense game (just for practice and experience). But I still have some troubles in shooting effects of towers. The idea is using lines to simulate a shoot from a tower to a ‘creep’. The tower would shoot the creep each one second, the line must to last long enough to make the effect looks like a laser.
This is my method: To make the tower shoot the creep and then wait a second before continuing shooting.

private long temp = 0;
private long prevTimeUpdate = -1;
private long temp1 = 0;
private long prevTimeUpdate1 = -1;

@Override
public void controlUpdate(float f)
{
  if(prevTimeUpdate == -1)
    prevTimeUpdate = System.currentTimeMillis();
  else{
    temp = (System.currentTimeMillis() - prevTimeUpdate)/100; 
    temp1 = (System.currentTimeMillis() - prevTimeUpdte)/100;
    if(temp == 10)
      //implement to make tower shoot the nearest creep
      //attach lines to shootNode
      if(temp1 == 11)
        //detach lines from shootNode
        shootNode.detachAllChildren();
   }
}

Because temp1 > temp, hence the lines exist long enough to make laser effect.
But as you can see in the video (the link below), some lines did not disappear right after I detached them from shootNode. Anyone have a good idea to remove this issue, thank you in advance.
This is the link of the video: FirstSmallGame(Demo) - YouTube
Thank you for watching and reading. By the way, I’m newbie here, nice to meet you all :smile:

Hi, i don’t own the book so i cannot help with this exact problem…
But in general.

It is better to always write complete statements including the curly brackets

if(expression){
  doSomething();
}
//instead of
if(expression)
  doSomething();

Mainly because it keeps the code consistent. (Maybe my personal opinion) But it also allows you to remove a line without changing the whole logic flow.

Also it is hard to follow if you use variables like temp, temp1, prevTimeUpdate, prevTimeUpdate1. Since we don’t know your code we can only guess what that code does. Having speaking names helps a lot.

in your code temp == temp1 so
if temp == 10, then temp1 == 11 is never rich because it’s 10 like temp

and you have a error because ‘prevTimeUpdte’ used to compute temp1 is not define

EDIT: for video/youtube link, put it in its own line then it will be embedded

EDIT2: Don’t use System.currentTimeMillis(); becaus it will not manage pause,… you should prefer to use tpf (accumulate) or application.getTimer()…

Sorry, I always use brackets with if statement, but at the time I was writing this post, I also was chatting with my friends on facebook too and I was using telex , so when I pressed “{”, it would be “ơ” in Vietnamese.
I named them temp and temp1 just as an example . Sorry about this inconvenience and thank you for your advice. :smiley:

I printed the result out many times, It worked accurately (I updated prevTimeUpdate and prevTimeUpdate1 in if ). But I still can not figure it out why there’re still some lines in the scene after I detach them from the shootNode. 1.This is my first post on jmonkey forum and I’m so sorry about my mistakes.
2. is there any tutorial on using app.getTimer()?
Anyway, thank you for replying soon.

idk if there is a tutorial but your can see javadoc getTimer

Don’t worry about forum mistake, and learn :wink: (you can edit your initial post to fix it : youtube, code,…)

Are you sur you detach ?

You can use tpf to decrease your timer. It is available in the update method of the appstates and controls.

a simple timer would look like:

float timeToLive;
public void update(float tpf){
  timeToLive=timeToLive-tpf;
  if(timeToLive<=0){
   //timer is over
  }
}

A complete example how a LaserRemoveControl could look like would be:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mygame;

import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;

/**
 *
 * @author michael
 */
public class LaserControl extends AbstractControl{
    float timeToLive;

    public LaserControl(float timeToLive) {
        this.timeToLive = timeToLive;
    }

    @Override
    protected void controlUpdate(float tpf) {
        timeToLive=timeToLive-tpf;
        if(timeToLive<0){
            removeLaserAndControl();
        }
    }

    
    private void removeLaserAndControl() {
        this.getSpatial().removeControl(this);
        this.getSpatial().removeFromParent();
    }
    
    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
  
    }
}

1 Like

Thank you zzuegg with a very helpful example. I’ll try this method and post the result soon.
Thank you david_bernard_31, we learn from mistake, and from each other as well, and I’m sure that I detach all the spatials in shootNode when temp == 11.

from you code snippet, temp/temp1 is never 11 in the if temp == 10.

This is my code:

@Override
    public void controlUpdate(float f)
    {
        if(getRemainBullets() <= 0)
        {
            spatial.removeControl(this);
        }
        if(prevTimeUpdate == -1)
        {
            prevTimeUpdate = System.currentTimeMillis();
        }
        else
        {
            long currentTime = System.currentTimeMillis();
            temp = currentTime - prevTimeUpdate;
            temp/=100;
            
            if(temp == 10)
            {
                prevTimeUpdate = currentTime;
            }
        }
        if(prevTimeUpdate1 == -1)
        {
            prevTimeUpdate1 = System.currentTimeMillis();
        }
        else
        {
            long currentTime = System.currentTimeMillis();
            temp1 = currentTime - prevTimeUpdate1;
           
            temp1/=100;
            if(temp1 >= 11)
            {
                prevTimeUpdate1 = currentTime; 
            }
        }
        
        if(charges.getRemainBullets() == 0)
        {
            charges.setRemainBullets(100);
        }
        else
        {
            List<Creep> creeps = appSate.getCreeps();
            
            if(creeps.size() == 0)
            {
                shootNode.detachAllChildren();
                
            }
            ArrayList<CreepControl> reachable = new ArrayList<CreepControl>();
            Geometry nearest = toIdentifyNearestCreep(appSate);
            if(nearest != null)
            {
                reachable.add(nearest.getControl(CreepControl.class));
            }
            if(reachable.size() > 0)
            {
                 if(temp >= 10)
                 {
                     Vector3f start = spatial.getLocalTranslation();
                     Vector3f end = nearest.getLocalTranslation();
                     charges.setRemainBullets(charges.getRemainBullets()-1);
                     appSate.getInformationPanel().setBullet(getIndex(), charges.getRemainBullets());
                     Geometry line = appSate.makeLine(start, end);
                     if(line == null)
                         System.out.println("Null");
                     shootNode.attachChild(line);
                     reachable.get(0).setCreepHealth(reachable.get(0).getCreepHealth()-charges.getDamageValue());
                     
                     if(reachable.get(0).getCreepHealth() <= 10)
                     {
                         System.out.println(nearest.getName() + " is going to die");
                     }
                 }
                
                 if(temp1 >= 11){
                     shootNode.detachAllChildren();
                 }
                
            }
        }
    }

public Geometry toIdentifyNearestCreep(GamePlayAppState state)
    {
        List<Creep> creeps = appSate.getCreeps();
        ArrayList<Float> distances = new ArrayList<Float>();
        if(creeps.size() != 0)
        {
            for(int count = 0; count < creeps.size(); ++count)
            {
                float distance = spatial.getLocalTranslation().distance(
                        creeps.get(count).getLocalTranslation());
                distances.add(distance);
            }
            Collections.sort(distances);
            float min = distances.get(0);
            for(int count = 0; count < creeps.size(); ++count)
            {
                float distance = spatial.getLocalTranslation().distance(
                        creeps.get(count).getLocalTranslation());
                if(distance <= min && distance <= ATTACK_DISTANCE)
                {
                    return creeps.get(count);
                }
            }
        }
        return null;
    }

I wonder : know how to use timer is necessary but why there’s no that information in the first 3 chapters of the book? or the authors want us to find this information from other sources?

Do you know what the ‘f’ parameter is? That may shed some light on how to track time. The book may have thought it was an easy leap but who knows.

I don’t know, I just remember that there is an example using the argument of controlUpdate in chapter two, but until now I still don’t understand it. Could you please explaining it for me?
I read the javadoc about app.getTimer().getTimePerFrame(), it says that : “Returns the time, in seconds, between the last call and the current one.” , I print it out every update, and I know that if I use a float to sum up all of these value, it will gradually reach a certain value. But the result is … I’m wrong. The sum is up and down unpredictably. Could you explain it ? I wonder if there is a difference between the argument of the controlUpdate method and the method app.getTimer().getTimePerFrame()?

Do you know how to look up javadoc? It’s pretty essential for Java development.

For example, http://javadoc.jmonkeyengine.org/com/jme3/scene/control/Control.html#update(float)

…which will tell you that the parameter is “time per frame”. To explain a bit, it’s the length of time (in fractions of a second) since the last time update was called.

(Though I see that AbstractControl is having update() delegate to controlUpdate() and the javadoc is much worse for that one… so it may not have helped much.)

This concept of “tpf” (ie: ‘time per frame’) is pretty universal in JME so it’s good to know it.

1 Like

I’ve just edited my question :)) .

Yes, but I think there should be an example to make it more clearer. (How to use it?, for what?, when?..)

Thank you guys for helping me, the problem is solved. Now the laser-effect works perfectly.
Thank you zzuegg and david_bernard_31. Now I can move to next chapter, chapter 4. :smile:
… but there’s still some confusion about argument tpf of controlUpdate and method app.getTimer().getTimePerFrame(), could you explain it for me and close this topic? :slight_smile:

In all of my years of JME development, I have never once had to access getTimer()… because “TimePerFrame” is passed to every update as tpf. It’s the time per frame… t… p… f… It’s the amount of time that has elapsed since the last time update was called. It measures the time between frames… thus it is the “time per frame”.

In other words, if your game is running at 60 frames per second then tpf will be 1/60th of a second.

timeElasped += tpf;

…it’s really not even slightly complicated.

1 Like

see jme3:beginner:hello_main_event_loop and do some search on the wiki

Thank you for brief explanation , got it.
But it seems that I’ve made you feel annoyed, sorry about this.