Main Menu

Finally...

Started by EgonOlsen, April 10, 2008, 11:08:22 PM

Previous topic - Next topic

JavaMan

Sweet. Awesome looking characters!

Just real quick, how did you get the fire exploding from the box?

EgonOlsen

The bombs explode, not the boxes...but anyway: It's a texture with 16 explosions in different stages (from small to big) that i'm using to texture billboarded planes that move away from the center of the bomb as it explodes. The center itself is a larger plane that uses the same texture. I'm constantly changing texture coordinates for all planes to make each of them grow and go back to a small fire ball again.

paulscode

Your quote from the Melssj5's Flier Match project gave me a thought:

Quote from: EgonOlsen on May 23, 2008, 08:05:25 PM
... i'm already transfering an event to the clients to inform them about sound type and location. I'm just not playing any sound right now.

It sounds like for this situation, it would be useful to have a method that creates a temporary sound source at a specified 3D location, which automatically deletes itself from memory when it is finished playing.  I thought I would add this capability into my sound library.

I just thought I would get your feedback on the idea before I add it to the library, because I think your concept of sending an event with a sound type and location is likely to be the way a lot of people will want to handle 3D sounds.  I was thinking I would make two different methods for creating temporary sources.  One method would play the source at the specified point from start to end (quick and easy way to play a short sounds, but might not be good for lengthy sounds).  In the second method, you would pass an Object3D as an arguement.  The source would bind itself to the Object3D (follow its position) until the sound finished playing.

Do you have any ideas or requirements from the sound library, specifically for your game?

JavaMan

Quote from: EgonOlsen on May 23, 2008, 11:33:56 PM
The bombs explode, not the boxes...but anyway: It's a texture with 16 explosions in different stages (from small to big) that i'm using to texture billboarded planes that move away from the center of the bomb as it explodes. The center itself is a larger plane that uses the same texture. I'm constantly changing texture coordinates for all planes to make each of them grow and go back to a small fire ball again.

Thanks for the explanation. I've wondered what billboarding is, this is a good use for it.

Jman

EgonOlsen

Quote from: paulscode on May 24, 2008, 02:02:49 AM
It sounds like for this situation, it would be useful to have a method that creates a temporary sound source at a specified 3D location, which automatically deletes itself from memory when it is finished playing.  I thought I would add this capability into my sound library.
Sound expensive to me...why do you have to delete the sournd from memory after playing? I haven't used 3D sound myself, but the simple sound stuff using OpenAL that i wrote for Paradroidz doesn't do this. It gets itself some channels, loads the sounds and plays it once on demand. That would be what i have in mind for Bomberman clone too. I need to place the sound source, but it doesn't have to move. It's sufficient if it stays where it started IMHO....at least for the sounds that i have in mind. I don't think that i'll add any ambient sounds. However, having the option would be nice.

Anyway, here are the sources for the Para-sound-stuff to show what i'm talking about in the above:

The Manager:

package naroth.sound;

import java.util.*;

import com.threed.jpct.*;

public class SoundManager {

   private Map sounds=null;
   private String lastName=null;
   private Sound lastSound=null;
   private boolean mute=false;

   public void setMute(boolean mute) {
      this.mute=mute;
   }

   public void disableSound(String name) {
      if (mute) {return;}
      Sound s=getSound(name);
      s.setMute(true);
   }

   public void enableSound(String name) {
     if (mute) {return;}
     Sound s=getSound(name);
     s.setMute(false);
  }


   public void addSound(String name, Sound sound) {
      if (!sounds.containsKey(name)) {
         sounds.put(name, sound);
      }
   }

   public SoundManager() {
      sounds=new HashMap();
   }

   public void playIfStopped(String name) {
      if (mute) {return;}
      Sound s=getSound(name);
      if (!s.isRunning()) {
         s.play();
      }
   }

   public void play(String name) {
      if (mute) {return;}
      getSound(name).play();
   }

   public void stop(String name) {
      if (mute) {return;}
      getSound(name).stop();
   }

   public boolean isRunning(String name) {
      if (mute) {return false;}
      return getSound(name).isRunning();
   }

   public void endLoop(String name) {
      if (mute) {return;}
      getSound(name).endLoop();
   }

   public void loop(String name) {
      if (mute) {return;}
      getSound(name).loop();
   }

   public void destroy() {
      Collection c=sounds.values();
      for (Iterator itty=c.iterator(); itty.hasNext();) {
         Sound s=(Sound) itty.next();
         s.destroy();
      }
      Logger.log("SoundManager destroyed!", Logger.MESSAGE);
      sounds=new HashMap();
   }

   private Sound getSound(String name) {
      if (name.equals(lastName)) {
         return lastSound;
      }
      Sound sound=(Sound) sounds.get(name);
      if (sound==null) {
         Logger.log("Sound with this name doesn't exists!", Logger.ERROR);
      }
      lastName=name;
      lastSound=sound;
      return sound;
   }

}



The interface:

package naroth.sound;

public interface Sound {

   boolean playedFine();
   boolean isRunning();
   void stop();
   void play();
   void endLoop();
   void loop();
   void destroy();
   void setMute(boolean is);
}


The OpenAL implementation:

package naroth.sound;

import java.io.*;
import java.nio.*;
import javax.sound.sampled.*;

import org.lwjgl.*;
import org.lwjgl.openal.*;
import com.threed.jpct.*;


public class OpenALSound implements Sound{

   private int buffer=-1;
   private boolean ok=false;

   private int lastIndex=-1;
   private int currentLoopIndex=-1;

   private static IntBuffer scratchBuffer = BufferUtils.createIntBuffer(256);
   private static int[] sources;
   private static int[] buffers=null;
   private static int sourceIndex;
   private static int channels=16;
   private static int instanceCount=0;
   private boolean mute=false;

   static {
      try {

         boolean ok=false;
         do {
            AL.create();
            sources=new int[channels];
            Logger.log("Trying with "+channels+" channels!", Logger.MESSAGE);
            scratchBuffer.limit(channels);
            AL10.alGenSources(scratchBuffer);
            if (AL10.alGetError()==AL10.AL_NO_ERROR) {
               scratchBuffer.rewind();
               scratchBuffer.get(sources);
               ok=true;
            } else {
               //AL10.alDeleteSources(scratchBuffer);
               AL.destroy();
               channels=channels>>1;
            }
         } while (!ok&&channels>0);

         if (channels!=0) {
            Logger.log("OpenAL initialized using "+channels+" channels!", Logger.MESSAGE);
         } else {
            Logger.log("Can't initialize one single channel!?", Logger.WARNING);
         }

      } catch (Exception e) {
         Logger.log("Unable to initialize OpenAL!", Logger.WARNING);
      }
   }

   public OpenALSound(InputStream is) {
      try {

         instanceCount++;
         IntBuffer scratchBuffer=BufferUtils.createIntBuffer(256);
         scratchBuffer.rewind().position(0).limit(1);
         AL10.alGenBuffers(scratchBuffer);
         buffer=scratchBuffer.get(0);

         byte[] data=null;

         Logger.log("Loading sound (OpenAL) from InputStream", Logger.MESSAGE);
         AudioInputStream soundStream=AudioSystem.getAudioInputStream(is);
         AudioFormat format=soundStream.getFormat();

         int len=(int) (format.getFrameSize()*soundStream.getFrameLength());
         data=new byte[len];
         int l=0;
         int lp=0;
         do {
            l=soundStream.read(data, lp, len);
            lp+=l;
         } while (l!=-1);

         // Welches Format?
         int chans = 0;
         boolean bit16=false;

         if (format.getChannels()==1) {
            if (format.getSampleSizeInBits()==8) {
               chans=AL10.AL_FORMAT_MONO8;
            } else if (format.getSampleSizeInBits()==16) {
               chans=AL10.AL_FORMAT_MONO16;
               bit16=true;
            }
         } else if (format.getChannels()==2) {
            if (format.getSampleSizeInBits()==8) {
               chans=AL10.AL_FORMAT_STEREO8;
            } else if (format.getSampleSizeInBits()==16) {
               chans=AL10.AL_FORMAT_STEREO16;
               bit16=true;
            }
         }

         ByteBuffer bb = ByteBuffer.allocateDirect(data.length);
         bb.order(ByteOrder.nativeOrder());
         ByteBuffer src=ByteBuffer.wrap(data);
         src.order(ByteOrder.LITTLE_ENDIAN);
         if (bit16) {
            ShortBuffer destShort=bb.asShortBuffer();
            ShortBuffer srcShort=src.asShortBuffer();
            while (srcShort.hasRemaining())
               destShort.put(srcShort.get());
         } else {
            while (src.hasRemaining())
               bb.put(src.get());
         }
         bb.rewind();

         AL10.alBufferData(buffer, chans, bb, (int) format.getSampleRate());

      } catch (Exception e) {
         Logger.log("Error loading sound: "+e, Logger.ERROR);
      }
   }

   public boolean playedFine() {
      return ok;
   }

   public boolean isRunning() {
      return lastIndex!=-1 && AL10.alGetSourcei(sources[lastIndex], AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING;
   }

   public void stop() {
      if (isRunning()) {
         AL10.alSourceStop(sources[lastIndex]);
         lastIndex=-1;
      }
   }

   public void setMute(boolean is) {
      mute=is;
   }

   public void play() {
      if (mute) { return; }
      int channel=getNextChannel();
      if (channel!=-1) {
         AL10.alSourcei(sources[channel], AL10.AL_BUFFER, buffer);
         AL10.alSourcePlay(sources[channel]);
      }
   }

   public void endLoop() {
      if (currentLoopIndex!=-1) {
         AL10.alSourcei(sources[currentLoopIndex], AL10.AL_LOOPING, 0);
         currentLoopIndex=-1;
      }
   }

   public void loop() {
      if (mute) { return; }
      if (currentLoopIndex==-1) {
         currentLoopIndex=getNextChannel();
         if (currentLoopIndex!=-1) {
            AL10.alSourcei(sources[currentLoopIndex], AL10.AL_BUFFER, buffer);
            AL10.alSourcei(sources[currentLoopIndex], AL10.AL_LOOPING, 1);
            AL10.alSourcePlay(sources[currentLoopIndex]);
         }
      }
   }

   private int getNextChannel() {
      int cnt=0;
      boolean ok=false;
      while (cnt<channels && !ok) {
         sourceIndex=(sourceIndex+1)%sources.length;
         cnt++;
         ok=AL10.alGetSourcei(sources[sourceIndex], AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING;
      }

      if (cnt>=channels) {
         ok=false;
         return -1;
      }
      ok=true;
      lastIndex=sourceIndex;
      return sourceIndex;
   }


   public synchronized void destroy() {

      if (buffers==null) {
         buffers=new int[instanceCount];
      }
      // Einsammeln der Buffer...
      buffers[instanceCount-1]=buffer;

      instanceCount--;
      if (instanceCount==0) {

         // Absolute Grütze, aber aufgrund der static-Eigenschaft der OpenAL-Anbindung
         // leider nicht anders zu machen.

         Logger.log("Destroying OpenAL!", Logger.MESSAGE);

         scratchBuffer.position(0).limit(sources.length);
         scratchBuffer.put(sources).flip();
         AL10.alSourceStop(scratchBuffer);

         AL10.alDeleteSources(scratchBuffer);
         sources=null;

         // Die Buffer selber löschen
         for (int i=0; i<buffers.length; i++) {
            scratchBuffer.position(0).limit(1);
            scratchBuffer.put(buffers[i]).flip();
            AL10.alDeleteBuffers(scratchBuffer);
         }

         buffers=null;
         AL.destroy();
      }
   }
}


paulscode

#50
Quote from: EgonOlsen on May 25, 2008, 10:02:39 AMSound expensive to me...why do you have to delete the sournd from memory after playing?

The sound would not be deleted from memory, just the source.  Sources, from what I can tell, do not take up a lot of memory, as they are made up of peripheral information like position, voice, gain, pan, etc.  The actual sound data is stored in a sound buffer, and that is loaded only once (in my library a sound is either loaded manually, or loaded automatically if you try to play a sound that has not been loaded yet).

Once upon a time I had considered using a system where sources are recycled, but eventually abandoned the idea.  You can only create around 16- 32 total sources, depending on your sound card, and each source is loaded with a single sound.  So if you ever wanted to have more sound effects than that, you need to be able to actively delete sources.  The problem of "limited sound effects" would be compounded exponentially if you wanted to play multiple instances of each sound effect from different positions -- each instance of each sound uses up one of those total sources.

I suppose one way this idea could be resurrected would be to hold on to sources as long as possible, until one of them had to be deleted to be used for a different sound effect or instance of a sound effect.  That way if you played the same sound a hundred times in a row from different positions, you would not necessarily be creating hundreds of different sources.  I may test this idea out to see if it has tangable performance benefits.

Quote from: EgonOlsen on May 25, 2008, 10:02:39 AM
I need to place the sound source, but it doesn't have to move. It's sufficient if it stays where it started IMHO....at least for the sounds that i have in mind. I don't think that i'll add any ambient sounds. However, having the option would be nice.

I finished creating an easy method for doing this:
String quickPlay( String filename, boolean toLoop, float x, float y, float z )

It creates a source for the sound "filename" at the specified 3D position, plays the source, and removes the source when it finishes playing (unless it is a looping source).  It automatically loads the sound "filename" if it hasn't been loaded yet.  The sound data for "filename" stays loaded, so you are not reloading the file every time you call the quickPlay method.  Return value is a random String name for the source, which you can use to manipulate the source while it is active, or just ignore it if you have no need to manipulate the source after creating it.  There are multiple versions of the method, which optionally allow you to specify attenuation model (ATTENUATION_NONE for ambient sound), fade distance, or rolloff factor.  I think this makes source creation on the fly quite simple, while still giving you options to do more complex things if required for a particular situation.

Anyway, sorry to get a little off topic on your thread.  Screen shots from your game are looking awesome - keep it up!

EgonOlsen

Sounds fine. I don't consider this to be off topic, because i want to use it for the game... ;D

paulscode

#52
I just now went back to look at code I wrote some time ago when I started on the sound stuff, and I noticed something in OpenAL I seem to have overlooked before:
AL10.alSourcei( mySource.get( 0 ), AL10.AL_BUFFER, myBuffer.get(0) );

It immediately jumped out at me, probably because I have been working with OpenAL for so long that it is becomming like a second language to me ;D.  Anyway, this line of code should allow buffers from different sound effects to be swapped in a source without actually deleting the entire source (duh!).  That in mind, I am going to go back and redo the way I handle sources, to ensure the best performance possible.  Thanks for bringing up the question, or I probably wouldn't have noticed this!  :D

--EDIT --
I just ran some tests, and it is a bit faster to use this method than what I was using.  Only requirement seems to be that the source is stopped before using the above line of code.  This is actually going to simplify things for me when I finish SoundSystem and get back to SoundManager, because I can now grab up all the available channels in the beginning, and not have to constantly recheck if channels are available every time a source is changed to a different sound effect.

EgonOlsen

#53
After a short pause (had to play Mass Effect first and replace the Radeon HD2900XT with a shiny new Radeon HD4870), i'm back on the game. In the last two days, i've added the basic bot support. The bots are currently using a kind of civil-servant-AI, i.e. they don't do anything apart from standing around. Adding movement will be the next big thing(tm).
However, that shouldn't be too hard, because they appear to the server (and all other clients) as normal clients, so i don't have to do anything special for the bots on that side.
I first planned to make each bot an indivudual client and make the bot clients share the same resources (they do need access to some geometry data, because they have to do collision stuff on their own) to save some memory, but that was too awkward. Sharing the resources would have required a huge amount of synchronization...not good. So i decided to make one bot client that manages an unlimited number of bots. I had to refactor the server to handle multiple players per client, but that wasn't that hard. I'm quite satisfied with the results so far.
Here's a shot of Bobby Bowden, Johnny Bower and Milton Bradley (i don't know who these guys are/were (expect for Milton) but they have their birthday on the 8th of November, like me...which is why i took those names...) in "action":


fireside

#54
The walls look bump mapped, are they?
Looking good, anyway. 
click here->Fireside 7 Games<-

raft


EgonOlsen

Fun with bots:

These are 50 of them. In a normal game, you'll have 0-5 or something like that. But it has to be able to handle 50 too, so i tried this. The AI is extremely dumb ATM. It uses A* to move and places bombs when blocked...most of the time, the bots blast themselves off.

EgonOlsen

I decided to rewrite the Bot AI from scratch. I had something that worked quite well in the wide open test levels, but when used in a more bomberman like level, it failed miserable. I had to face it: I created idiots...idiots following a complex set of rules, but still idiots... :'(

Melssj5

like many Bureaucrats and Politicians.
Nada por ahora

JavaMan

complex rules for politicians==whatever is best for them