Git Product home page Git Product logo

libgdx / gdx-ai Goto Github PK

View Code? Open in Web Editor NEW
1.2K 101.0 238.0 1.46 MB

Artificial Intelligence framework for games based on libGDX or not. Features: Steering Behaviors, Formation Motion, Pathfinding, Behavior Trees and Finite State Machines

License: Apache License 2.0

Java 99.15% Ragel 0.85%
libgdx pathfinding steering-behaviors formation-motion behavior-trees java artificial-intelligence framework npc state-machines

gdx-ai's Introduction

libGDX Logo

GitHub Actions Build Status

Latest Version Snapshots

Discord Chat

Cross-platform Game Development Framework

libGDX is a cross-platform Java game development framework based on OpenGL (ES), designed for Windows, Linux, macOS, Android, web browsers, and iOS. It provides a robust and well-established environment for rapid prototyping and iterative development. Unlike other frameworks, libGDX does not impose a specific design or coding style, allowing you the freedom to create games according to your preferences.

Open Source, Feature Packed, and Fostering a Large Third-Party Ecosystem

libGDX is released under the Apache 2.0 License, offering unrestricted usage in both commercial and non-commercial projects. While not mandatory, we appreciate any credit given to libGDX when you release a game or app using it. Check out our showcase for a selection of popular libGDX-powered games. With libGDX, you gain access to a comprehensive set of tools and features to develop multi-platform 2D and 3D games using Java.

Moreover, libGDX boasts a vibrant third-party ecosystem, with numerous tools and libraries that streamline development tasks. Explore the awesome-libgdx repository for a curated list of libGDX-centered libraries, serving as an excellent starting point for newcomers in the libGDX community.

An example game created with libGDX: Pathway by Robotality. Discover more captivating games in our Showcase.

Getting Started with libGDX / Documentation

Thanks to Gradle, you can easily set up libGDX without the need to download the framework itself. Your favorite build tool can handle everything for you. Additionally, we offer a convenient setup tool that automates project creation and downloads all the necessary components. Check out our website for instructions on getting started or refer to our comprehensive wiki.

We provide the libGDX javadocs online for easy reference. Additionally, the javadocs are bundled with every libGDX distribution, ensuring smooth integration with your preferred IDE.

Community & Contribution

Stay up to date with the latest libGDX news by following our blog. For engaging discussions and support, join our official libGDX Discord.

Reporting Issues

Use the Issue Tracker here on GitHub to report any issues you encounter. Before submitting, please read our Getting Help guide, which walks you through the process of reporting an issue effectively.

Contributing to the Codebase

libGDX benefits greatly from contributions made by our dedicated developer community. We appreciate any assistance in making libGDX even better. Check out the CONTRIBUTING.md file for details on how to contribute. Note that contributing involves working directly with libGDX's source code, a process that regular users do not typically undertake. Refer to the Working with the Source article for guidance.

You can also support our infrastructure (build server, web server, test devices) by contributing financially through our Patreon!

gdx-ai's People

Contributors

avianey avatar badlogic avatar crykn avatar davebaol avatar dgzt avatar florianbaethge avatar genhis avatar jackrainy avatar jhoukem avatar lukz avatar maddinpsy avatar mgsx-dev avatar niemandkun avatar outofcoffee avatar piotr-j avatar reloecc avatar shibabandit avatar tom-ski avatar tomcashman avatar tommyettinger avatar tomvangreen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gdx-ai's Issues

IndexedAStarPathFinder uses == where .equals() might be better

In IndexedAStarPathFinder#search there is this line:

// Terminate if we reached the goal node
if (current.node == endNode) return true;

This only works if nodes don't change identity. Depending on how the user implements methods such as getConnections, the nodes might change identity while still remaining logically equal (eg if the type N is the user's custom Coordinate class). I ran into this problem myself, but fixed it by caching my coordinate objects (which I should have been doing anyway), but it's potentially confusing for less experienced developers. If there isn't a compelling reason to to do otherwise, it might be better to replace the == with .equals.

NOTICE file

@badlogic is the NOTICE file in this repo really the NOTICE file you want people to redistribute?

Example in its specific repo

Please ensure you have given all the following requested information in your report.

Issue details

Please provide the details of your issue

Reproduction steps/code

Please provide the steps to reproduce this issue, or even better, a SSCCE that reproduces your issue.

Version of gdx-ai and/or relevant dependencies

Please provide the version(s) affected.

Stacktrace

//Please provide the stacktrace if applicable 

FSM: provide implementation of StateMachine with the "strict" change of state

It is common case scenario when you want to trigger state change only if current state is different than new one:

if (!mStateMachine.isInState(PlayerState.RUN)) {
    mStateMachine.changeState(PlayerState.RUN);
}

What's about to add some default implementaion of the state machine with this check to be available out of the box? Something like this:

public class StrictStateMachine<E> extends DefaultStateMachine<E> {

    /**
     * @see DefaultStateMachine#DefaultStateMachine(E)
     */
     public StrictStateMachine(E owner) {
        super(owner, null, null);
    }

    /**
     * @see DefaultStateMachine#DefaultStateMachine(E, State)
     */
    public StrictStateMachine(E owner, State<E> initialState) {
        super(owner, initialState, null);
    }

    /**
     * @see DefaultStateMachine#DefaultStateMachine(E, State, State)
     */
    public StrictStateMachine(E owner, State<E> initialState, State<E> globalState) {
        super(owner, initialState, globalState);
    }

    @Override
    public void changeState(State<E> newState) {
        if (!isInState(newState)) {
            super.changeState(newState);
        }
    }
}

MessageDispatcher shouldn't be a Singleton

MessageDispatcher doesn't work with system resources so making it a Singleton is an anti-pattern, since it heavily limits it's usage.
http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons

It makes perfect sense, for example, to have separate MessageDispatchers for different parts of the app - one for ui, other one for ai - cause that makes message constants easier to maintain as they aren't clutter together, and it makes it possible to configure time granularity differently for different dispatchers.

Going beyond running the tests

So I really like what where the AI extension stands and would love to try some features out. However it seems to me like there is no documentation beyond the tests on how someone would implement something like say a steering behavior. It's nearly impossible for me to pull working code to demo in a new project from the tests. How would I set up say a simple LookWhereYourGoingTest in a new libgdx project? I'm sorry if I am missing something really obvious just need some help.

Add Provider mechanism for Telegram

Like square Otto bus @provide annotation (and Guava event bus), it would be great if we can define Providers that will send a Telegram automatically when listeners registered for a msg :

MessageDispatcher.getInstance().addProviders(this, int ... msgs)
interface Provider {
   Telegraph provides(int msg);
}

When registering msgs, listeners will get Telegraph from providers registered for the given msgs.

In games, entities are living : they are created, destroyed, evolving, changing states, ... When communicating together through Telegrams some newly created entities will not get notified of some important Telegram sent by other entities just before they register. With providers, they will get the last emitted Telegram of the desired type as soon as they register MessageDispatcher.getInstance().addListeners.... REDUCING COUPLING BETWEEN ENTITIES A LOT !

Support for selectable pathdining on custom node types

Id like to start discussion about this topic, that im not even sure how to title correctly.
Take for example the pfa tests. We have TiledNodes of certain types. We pathfind on TILE_FLOOR. What if we want have units that can only walk on TILE_WALL or on both?
What would be optimal way of implementing ability to select on what we pathfind? Should something like this even be supported by the library, or should it be left to the user to implement?

GWT is broken in version 1.7.0

Looks like the cause is the new GdxFileSystem. Here is the output that I get when trying to compile for GWT:

16:00:58.902 [QUIET] [system.out]    Validating units:
16:00:58.906 [QUIET] [system.out]       [ERROR] Errors in 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/GdxFileSystem.java'
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 41: No source code is available for type com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver; did you forget to inherit a required module?
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 49: No source code is available for type com.badlogic.gdx.assets.loaders.resolvers.LocalFileHandleResolver; did you forget to inherit a required module?
16:00:58.906 [QUIET] [system.out]       [ERROR] Errors in 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/StandaloneFileSystem.java'
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 69: The constructor FileHandle(String, Files.FileType) is undefined
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 73: The constructor FileHandle(File, Files.FileType) is undefined
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 77: file cannot be resolved
16:00:58.906 [QUIET] [system.out]          [ERROR] Line 77: type cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 78: file cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 78: type cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 82: file cannot be resolved
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 83: file cannot be resolved
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 83: type cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 87: file cannot be resolved
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 89: type cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 94: type cannot be resolved to a variable
16:00:58.907 [QUIET] [system.out]          [ERROR] Line 98: type cannot be resolved to a variable
16:00:58.908 [QUIET] [system.out]          [ERROR] Line 98: file cannot be resolved
16:00:58.908 [QUIET] [system.out]          [ERROR] Line 99: type cannot be resolved to a variable
16:00:58.908 [QUIET] [system.out]          [ERROR] Line 99: file cannot be resolved
16:00:58.908 [QUIET] [system.out]          [ERROR] Line 100: file cannot be resolved to a variable
16:00:58.908 [QUIET] [system.out]       [ERROR] Errors in 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/btree/utils/DistributionAdapters.java'
16:00:58.909 [QUIET] [system.out]          [ERROR] Line 463: The method countTokens() is undefined for the type StringTokenizer
16:00:58.950 [QUIET] [system.out]    Removing invalidated units
16:00:59.004 [QUIET] [system.out]       Compilation unit 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/btree/utils/BehaviorTreeParser.java' is removed due to invalid reference(s):
16:00:59.004 [QUIET] [system.out]          com.badlogic.gdx.ai.btree.utils.DistributionAdapters
16:00:59.021 [QUIET] [system.out]       Compilation unit 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/btree/utils/BehaviorTreeLibrary.java' is removed due to invalid reference(s):
16:00:59.022 [QUIET] [system.out]          com.badlogic.gdx.ai.btree.utils.BehaviorTreeParser
16:00:59.022 [QUIET] [system.out]       Compilation unit 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/btree/utils/BehaviorTreeLoader.java' is removed due to invalid reference(s):
16:00:59.022 [QUIET] [system.out]          com.badlogic.gdx.ai.btree.utils.BehaviorTreeParser
16:00:59.041 [QUIET] [system.out]       Compilation unit 'jar:file:/Users/baz/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.7.0/fedc63e153ddbd33ea94c1ed379f811c6688269d/gdx-ai-1.7.0-sources.jar!/com/badlogic/gdx/ai/btree/utils/BehaviorTreeLibraryManager.java' is removed due to invalid reference(s):
16:00:59.041 [QUIET] [system.out]          com.badlogic.gdx.ai.btree.utils.BehaviorTreeLibrary
16:00:59.217 [QUIET] [system.out] Wrote 3854 units to persistent cache.
16:01:00.628 [QUIET] [system.out]    Finding entry point classes
16:01:00.629 [QUIET] [system.out]    [ERROR] Aborting compile due to errors in some input files

ReachOrientation SteerBehavior 180ยบ bug

There is a issue with the way gdx ai calculates rotation, it asumes rotation values are continuous, and thats not the case with normalized angles or with the jump from 360ยบ to 0ยบ.(im using degrees instead of radians for ease of explanation)
When the owner is at 350ยบ and wants to face a target that is at 10ยบ, instead of moving 20ยบ degrees it reverses movement, because he doest not know that 10ยบ is the same that 370ยบ and just needs to move 20ยบ, he thinks that just has to reverse the angular velocity to get to the 20ยบ.

i fixed the problem localy using normalized angles in my rotations, and just adding 2Pi to the targetOrientation if his value was lower than owner.getOrientation() int the reachOrientation method.

Suggestion: allow us to provide our own Random object

I have a game I'm working on with procedural generation. To make testing easier I like to use the same random seed on every run.

Instead of using MathUtils.random for doing random stuff in things like the Wander steering behavior, it would be great if it used something like GdxAi.random(), and then allow me to do something like GdxAi.setRandomInstance(myRandom); so that the ai will play out the same way every time with all the other aspects of my game.

edit: also to add, this would be useful for games with save/load functionality. Without being able to set the random instance, players could reload their game over and over to get different outcomes and "cheat" the game.

DefaultIndexedGraph.getConnections could be simplified

I was looking through the pathfinding code and found this:

public Array<Connection<N>> getConnections (N fromNode) {
  return nodes.get(fromNode.getIndex()).getConnections();
}

fromNode.getIndex returns the index into the nodes-array. So nodes.get(fromNode.getIndex()) will return the fromNode itself. Basically you could just do the following

public Array<Connection<N>> getConnections (N fromNode) {
  return fromNode.getConnections();
}

Perhaps I overlooked something, but as it seems, the whole nodes-Array of the DefaultIndexedGraph is not really used.

Removing a Telegram listener during discharge will cause other listeners to be ignored

Issue details

If you have two listeners to a message, and the first one removes itself upon receiving that message, the other listener will not get the message delivered.

I believe it is because of the way the for loop is constructed in the discharge method

    Array<Telegraph> listeners = msgListeners.get(telegram.message);
    if (listeners != null) {
        for (int i = 0; i < listeners.size; i++) {
            if (listeners.get(i).handleMessage(telegram)) {
                 handledCount++;
            }
        }
    }

Reproduction steps/code

Create two listeners to the same message
Have the first remove itself as a listener during the onMessageRecieved call
The second listener will not get the call

Version of gdx-ai and/or relevant dependencies

1.8.0

Add BehaviorTree.reset() Method

Hi!

In my program i am interrupting the execution from BehaviorTree while they are running. After that i want to reuse the BehaviorTree instance (or pool it).

At the moment im am interating through the nodes of the tree with this method:

    @SuppressWarnings("rawtypes")
    private void reset(Task<?> task) {

        if (!ClassReflection.isInstance(Sequence.class, task) && !ClassReflection.isInstance(Decorator.class, task)
                && !ClassReflection.isInstance(BranchTask.class, task))
            task.childRunning(null, null);

        if (task instanceof Poolable)
            ((Poolable) task).reset();

        for (int i = 0; i < task.getChildCount(); i++) {
            Task child = task.getChild(i);
            reset(child);
        }
    }

At the moment i am abusing the childRunning() method to set the runningTask to null. But i had to add the checks to not get a nullpointer when calling childRunning().

Is there a better way to reset a BehaviorTree or can you provide a method to do this?

Regarding CircularBuffer's store() method

Hi!

I wanted to use the ai.utils.CircularBuffer as part of my project, however, I encountered some issues:

Regarding CircularBuffer's store() method ( https://github.com/libgdx/gdx-ai/blob/master/gdx-ai/src/com/badlogic/gdx/ai/utils/CircularBuffer.java ):

    public boolean store (T item) {
        if (isFull()) {
            if (!resizable) return false;

            // Resize this queue
            resize(Math.max(8, (int)(items.length * 1.75f)));
        }
        items[tail++] = item;
        if (tail == items.length) tail = 0;
        return true;
    }

Why does it abort and return false when the circular buffer is full and not resizable? Ring buffers would usually overwrite the oldest entry, but apparently this never happens here.

Also, if the buffer is full and resizable, it will get resized. Thus f (tail == items.length) tail = 0; will probably never get executed.

Another problem:

        CircularBuffer<Float> buffer = new CircularBuffer<Float>(3, false);

        System.out.println(buffer.store(3.2f));
        System.out.println(buffer.store(3.2f));
        System.out.println(buffer.store(3.2f));
        System.out.println(buffer.store(3.2f));

prints true, true, false, false.

So, not only does the "circular" buffer simply don't store new values like a circular buffer should but it also stores one element less than its capacity. This is likely caused by a bug in the isFull() method.

Allow dispatching messages without the need of a Telegraph

Right now, sending a message using the MessageDispatcher#dispatchMessage() method requires specifying a sender, which is an instance of Telegraph.

A very common case scenario is implementing the interface on the very same class you are dispatching the message from. So, typically, you end up with something like:

MessageManager.getInstance().dispatchMessage(this, 1);

Equally common is having classes that only generate events and do not handle any type of message. Currently, those classes are forced to implement Telegraph in order to broadcast events (or to have access to some other unnecessary Telegraph instance). So, you find yourself writing this

    @Override
    public boolean handleMessage(final Telegram msg) {
        return false;
    }

very frequently, just to dispatch a message. I tend to think the API could benefit from removing the need of having a mandatory sender parameter. If you do not specify one, just dispatch the message to every possible recipient from an anonymous sender.

What do you think?

Replacing Array by Iterable where possible.

When using the framework out of libgdx, using it collections can be a "problem" (the same collection isn't shareable between not-libgdx-collections-dependant code and dependant one).

In RadiusProximity and so, in ProximityBase the agent collection used is a 'com.badlogic.gdx.utils.Array' where it could be 'java.lang.Iterable'.

The only usage of the collection in those is to iterate it like:

for (int i = 0; i < agentCount; i++) {
    Steerable<T> currentAgent = agents.get(i);

I would suggest to use the List interface so you could continue using the ".get" method but Array doesn't implement it. However, I think in this cases is it perfectly possible the implicit iteration way:

for (Steerable<T> currentAgent : agents)

As I can see, the default iteration system on 'com.badlogic.gdx.utils.Array' reuses the same iteration object to be gc friendly.

GWT Compatibility appears broken

Compiling module com.supercookie.game.GdxDefinition
   Validating units:
      [ERROR] Errors in 'jar:file:/C:/Users/Cookie/.gradle/caches/modules-2/files-2.1/com.badlogicgames.gdx/gdx-ai/1.3.2-SNAPSHOT/8263c0e657fd93f1c5f88216efd14cd2e9548f5e/gdx-ai-1.3.2-SNAPSHOT-sources.jar!/com/badlogic/gdx/ai/btree/parser/TreeLineProcessor.java'
         [ERROR] Line 59: The method newInstance() is undefined for the type Class
         [ERROR] Line 63: No source code is available for type java.lang.InstantiationException; did you forget to inherit a required module?
   [ERROR] Aborting compile due to errors in some input files
:html:compileGwt FAILED

Am I missing something here since the move? I am working against 1.3.2-SNAPSHOT

Bug in BTree Selector

Issue details

The guard in a selector is not handed correctly. The guard is checked and if it fails the run method is called anyway. In SingleRunningChildBranch line 88 is an else missing. The run method is called later on the child that succeeded. This child is called twice.
I could only see it in the debugger, when executing step by step. I used to set a breakpoint in LeafTask:42, this gets called twice for the last child.

Reproduction steps/code

root
  selector
    (care urgentProb:0) walk
    bark # bark will be called two times

Version of gdx-ai and/or relevant dependencies

gdx-ai1.8.0 and below

Solution

I solved it myself with the following code:
SingleRunningChildBranch: Line 82

                } else {
                    runningChild = children.get(currentChildIndex);
                }
                runningChild.setControl(this);
                runningChild.start();
                if (runningChild.checkGuard(this))
                    run();
                else
                    runningChild.fail();
            } else {
                // Should never happen; this case must be handled by subclasses in childXXX methods
            }
        }
    }

instead of

                } else {
                    runningChild = children.get(currentChildIndex);
                }
                runningChild.setControl(this);
                runningChild.start();
                if (!runningChild.checkGuard(this))
                    runningChild.fail();
                run();
            } else {
                // Should never happen; this case must be handled by subclasses in childXXX methods
            }
        }
    }

Serializable and Deserializable Messages - MessageDispatcher

Following this thread:
http://www.badlogicgames.com/forum/viewtopic.php?f=17&t=16550#p70988

Two things would be needed:

  1. Make previousState in MessageDispatcher protected, so a method for setInitialState can be made (or better, add it so there's no need to extend it :D)
  2. MessageDispatcher needs a method that returns an array of "Savable" objects, with the information of the pending messages, so they can be saved (serializing them or whatever).

Thank you! :)

Gitter

Hey everyone,

Would it be possible to get a Gitter or some other form of Chat system for GDX-Ai?

Just curious,
HeadClot

Provide context to the pathfinder.

For the moment the pathfinder finds connection between nodes by calling getConnections() on the IndexedNode.

My problem is that there is no context to decide if a connection should exist or not or to provide a different cost for the connection.

Simple example, you could imagine that some units could go over water cells while other cannot. Or that some agent have a different cost for a specific connection. Problem is, there is no way to tell for which entity (i.e. context) the pathfinding is running.

Discussing Behavior Trees

I've opened this to discuss behavior trees API enhancements.

@implicit-invocation
Resuming discussion #4 (comment) ...
Not sure why you don't like the XML format, but I think that it has some advantages:

  • comments are supported which can be really useful
  • it's more powerful than json or any inline tab-based format.
  • being it a standard format, external tools like editors (written in any language) can easily read/write a behavior tree.

So I'm playing with the XML format just to see what can be done.
Currently I can successfully load a behavior tree from this file:

<BehaviorTree>
  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.BarkTask" as="Bark"/>
  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.CareTask" as="Care"/>
  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.MarkTask" as="Mark"/>
  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.RestTask" as="Rest"/>
  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.WalkTask" as="Walk"/>
  <Root>
    <Selector>
      <Parallel>
        <com.badlogic.gdx.ai.tests.btree.dogtasks.CareTask urgentProb="0.8"/>
        <AlwaysFail>
          <com.badlogic.gdx.ai.tests.btree.dogtasks.RestTask/>
        </AlwaysFail>
      </Parallel>
      <Sequence>
        <Bark times="3"/>
        <Walk/>
        <Bark/> <!-- times defaults to 1, see BarkTask source code -->
        <com.badlogic.gdx.ai.tests.btree.dogtasks.MarkTask/>
      </Sequence>
    </Selector>
  </Root>
</BehaviorTree>

I added the "Import" tag to improve readability. It allows you to use the given alias in place of the fully qualified class name of the task. Actually Selector, Parallel, etc.. are predefined imports. Also, the "as" attribute is optional, meaning that the simple class name is used as the alias, i.e.

  <Import task="com.badlogic.gdx.ai.tests.btree.dogtasks.BarkTask"/>

creates the task alias "BarkTask".
Also, I added task parameters, see urgentProb in CareTask and times in BarkTask.
The attribute value is parsed according to the type of the corresponding field of the task class. For example, urgentProb is a float and times is an int. Supported types are: int, Integer, float, Float, boolean, Boolean, long, Long, double, Double, short, Short, char, Character, byte, Byte, and String.

Of course, we can maintain both formalisms as long as they have the same structural features. I mean, unlike task parameters, imports are just a syntactic sugar so they are not mandatory for the inline tab-based formalism.

I think we can use a "btree" branch in order to experiment with BT improvements while keeping the master branch clean.

Hierarchical pathfinding question

I have seen your implementation of the hierarchical pathfinding and mine is slightly different. I just have a tilemap and each tile has a node with connections. Now I want to divide my map in "sectors" and build nodes on the edges where there is a entrance.

  • Is there some implementation already available that does this for me? Since this would be pretty much the same for each basic tile map I would expect something like this is already in place. Something like this:

    HPF doodle (forgot a view connections).

If so, I would like to know :)

If not:

  • So i lookup all the borders and add level 2 nodes where there is an entrance. I (try) to connect all those within a "sector" and the bordering neighbors.
  • Is this sufficient, does the pathfinder find paths in between the level 2 nodes once it finds a level 2 path? Or do I need to store the actual shortest path in the connections? I can imagine it is the former since looking up A -> B -> C -> ... -> Z is much faster then looking up A -> Z where the open list will grow out of control.

I see you store all the nodes in a single array and use an offset for higher levels. However I want to make a dynamic map. And once a node changes it will update it surrounding nodes and if level 2 changes as well it does the same. But that means I need something more flexible then a ArrayList or (like I do on the 1st level) create all possible nodes and alter there connections.
I would appreciate some pointers in the right direction.

Thanks in advance,

Circle formation of 1 element fails

Hi, because of the way radius is calculated in CirclePattern:

float radius = memberRadius / (float)Math.sin(Math.PI / numberOfSlots);

it get divided by 0 if there is only 1 slot. We should be able to generalize formation even for 1 slot only.

Steering ReachOrientation sets linear velocity to zero

The face behavior is now unusable with linear velocity behaviors and since linear velocity behaviors zero the angular component and face behavior zeroes the linear component. Furthermore, it is impossible to extend ReachOrientation behavior, because it uses the linear component for temp calculations.

In my use case, when player moves a ship, both face and arrive behavior are used simultaneously. I feed these values to Box2d that then does the physics for me.

Currently I can work around this by using calling calculate steering twice: once for movement, and once for rotation. This solution is not bad, I am just not sure if it is intended, since it seems obvious to use Face behavior with seek and evade behaviors.

Shared behavior trees

When using the same behavior tree in many objects is a waste of resources to have the tree instantiated for all of them. Instead, it could be great to have them updated like:

public void start(T object);
public Status execute(T object);

where the object is the blackboard object being updated like:

tree.step(object);

This way, the same tree could be used for as many objects as they are needed. In lot of games it is normal to use the same ai tree for lot of entities instead of having a different one for every of them.

This is an easy enhancement that I think is very useful.

Sample of "exhaust" loop

Here is the gist: https://gist.github.com/scooterman/b03dfe1560cfc3b977a3

This is how I've implemented the loop (as a branch, heh) that suffices my needs. I'm asking for guidance (if there is a desire to use it on the main lib) on how to integrate it. The first thing should be able pass the leaf as a parameter instead of the first child, something like:

exhaust leaf: "entitiesInRange"

Are steering behaviors working with Box2D linear damping?

I'm working on 2D top down game and I'm trying to make heavy enemy which slowly builds up his speed and crash into the wall. His body can be affected by external forces (push back on hit, attract by magnet etc.) so he has pretty big linear damping.

I'm trying to achieve described behavior by using Arrive Steering Behavior but I can't make him accelerate slowly.

Is there any way to make Arrive SB work in such situation?

I've made Box2dArriveTest with my parameters: Box2dCustomArriveTest.zip

character = createSteeringEntity(world, container.greenFish, false);
character.setMaxLinearSpeed(8);
character.setMaxLinearAcceleration(40);

MassData massData = new MassData();
massData.center.set(character.getBody().getMassData().center);
massData.I = character.getBody().getMassData().I;
massData.mass = 1f;

...

final Arrive<Vector2> arriveSB = new Arrive<Vector2>(character, target) //
    .setTimeToTarget(0.1f) //
    .setArrivalTolerance(0.001f) //
    .setDecelerationRadius(0.5f);

NaN return

if (steeringBehavior != null) {
        steeringBehavior.calculateSteering(steeringOutput);
        if (this.body != null && !steeringOutput.linear.isZero()) {
            System.out.println(steeringOutput.linear);
            this.body.applyForceToCenter(steeringOutput.linear, true);
        }
        // applySteering(steeringOutput, deltaTime);
    }

I'm using gdx-ai 1.5.0

calculateSteering is returning a NaN,NaN vector.
I am using the example Box2dRadiusProximity with the CollisionAvoidance steering behavior. No errors are thrown, my box2d body just disappears once i approach the radius distance, very weird behavior. I don't understand where the issue is spawning from

Parallel Task

The docs in Parallel states:

parallel task will succeed if all the children succeed, fail if one of the children fail.

but that doesn't seem to be the case. I have this:

Parallel<Brain> parallel = new Parallel<>();
parallel.addChild(new TaskA());
parallel.addChild(new TaskB());     

I would expect parallel to stop if TaskA fails, but that doesn't happen. Is it a bug or am i misunderstanding something?
Full example

Vector3 and Vector2 as interface enhancement.

It would be great if we could work with Vector2 and Vector3 being interfaces so we could share code between different platforms without having to refactor things or having to duplicate vectors.

It can be a good practice to use local attributes, like velocity or positions but in some cases, for memory optimization purposes, or even different usage/programming styles (ie: having the position shared between the steering agent and the visual object so it automatically changes the value) it is comfortable to have the same object.

Using, for example the JME3 engine we have a Vector3f (which is the equivalent to the Vector3). Well, if I want to use the Vector3f I have to make the conversions from them or make a thin wrapper/decorator:
"MyVector3 implements Vector".

This is just fine but the same problem can be found: the code must be done or with Vector3 or with MyVector3 so if I want to put/share that code between engines it is not so straight-forward. However, if Vector3 were an interface (MyVector3 implements Vector3), I could do my code and share it between engines without being aware about refactoring it in so much places and without wasting "so much" resources.

Discussing pathfinding API

EDIT by davebaol

Hello, now that you have your own extension, maybe you can start considering an A* implementation to go together with that wonderful steering, right? :)

Behavior Tree Default Constructor

On initial observation, seems like the default constructor puts the BehaviorTree into a state where the rootTask field cannot be set.

Physics incoherence in examples

Hi,

I was looking the examples on steering behavior, and I found ans issue. I'm not 100% sure I got it right, but let me explain. On the Scene2D example, the steeringOutput is calculated, and scaled with time and added to the velocity.

private void applySteering (SteeringAcceleration<Vector2> steering, float time) {
        // Update position and linear velocity. Velocity is trimmed to maximum speed
        position.mulAdd(linearVelocity, time);
        linearVelocity.mulAdd(steering.linear, time).limit(getMaxLinearSpeed());
}

So the steering linear is treated as a force. On the other hand, on the example with box2D, the steeringOutup is calculated, then scaled to deltaTime, and finaly applyed as a force to the body:

protected void applySteering (SteeringAcceleration<Vector2> steering, float deltaTime) {
        boolean anyAccelerations = false;

        // Update position and linear velocity.
        if (!steeringOutput.linear.isZero()) {
            Vector2 force = steeringOutput.linear.scl(deltaTime);
            body.applyForceToCenter(force, true);
            anyAccelerations = true;
}

In this case the steeringOutput.linear is scaled to delta before is used as a force. Then the box2D will scale again with dalta before applying to velocity. The result is a much weaker force.

As I sad, I am not sure if I've wrong understood it, or it's a mistake on the examples. And if so, which of the to cases should we use in our code?

Thanks,
StefanW

Parsing of behavior tree data fails when debug is set

I discovered that parsing of behavior tree data fails when BehaviorTreeParser debug is set to anything but BehaviorTreeParser.DEBUG_NONE.

I traced the problem down to line 327 in BehaviorTreeReader.java:

if (debug) System.out.println("endLine: indent: " + indent + " taskName: " + statementName + " data[" + p + "]" + data[p]);

Which throws an ArrayIndexOutOfBoundsException.

Not sure if it makes any difference, but I fed a string of tree data to BehaviorTreeParser instead of a file. The string loads fine when debug is off.

Interrupted PathfinderRequest never finishes

On snapshot, PathfinderRequest#statusChanged is set to true by PathfinderRequest#changeStatus but never reset.

This causes each call to IndexedAStarPathFinder#search(request, timeToRun) to restart the search, so a route that takes longer to calculate than timeToRun will never finish

Working around it for now by setting stateChanged by hand after the first search, but assume this is something the finder is supposed to do.

request = new PathFinderRequest<>(..);
request.changeStatus(PathFinderRequest.SEARCH_INITIALIZED);
finder.search(request, TimeUtils.millisToNanos(25))

Text format behavior trees defining args enhancement

I've been looking around but It doesn't seem to be possible to use variables when defining trees by text foramt. What I'm really trying is to do is, for example (taken from the "dog example"):

subtree name:"actOnYourOwn"
  selector
    (random success:0.1) piss
    parallel policy:"selector"
      wait seconds:"uniform,3,6"
      randomSelector
        wander gait:$(gaitType)
        wander gait:"walk"
        lieDown
        sit

Where I could import the "actOnYourOwn" tree in a manner that $(gaitType) would be replaced but a given value, allowing to create general tasks to be used by many different entity personalities.

Calculate distance between steerables in a 2D wrapped around environment

Imagine our characters is moving on the surface of a sphere. So there will be 2 ways to calculate distance between them. But all of gdx-ai behaviors use owner.getPosition() to calculate distance.

I think Steerable interface should have getDistance(point:Vector2):float getDistanceSq(point:Vector2):float method and behaviors will use them to decide how to seek, evade, flee...

Problem with Arrive steering behavior

Hi,

i have found the following problem with the Arrive Steering Behavior:

When the steerable is approaching the target, the steeringOutput begins to "jump around". You can see this, when you look at the steeringOutput angle:

steeringOutput= [0.09993597:0.0035781164], len= 0.10000001, angle:2.0505471
steeringOutput= [0.099935964:0.003578156], len= 0.1, angle:2.05057
steeringOutput= [0.09993597:0.0035780247], len= 0.1, angle:2.0504947
steeringOutput= [0.09993595:0.0035785697], len= 0.1, angle:2.050807
steeringOutput= [0.09993608:0.0035749506], len= 0.10000001, angle:2.0487323
steeringOutput= [0.063587725:0.0023101782], len= 0.06362968, angle:2.0806732
steeringOutput= [0.09993937:0.0034819124], len= 0.10000001, angle:1.9953921
steeringOutput= [-0.07271707:-0.0016047317], len= 0.07273477, angle:181.26422
steeringOutput= [0.099956475:0.0029501538], len= 0.1, angle:1.6905588
steeringOutput= [-0.09558454:0.002485239], len= 0.09561685, angle:178.51062
steeringOutput= [0.09999966:2.6154998E-4], len= 0.1, angle:0.14985727
steeringOutput= [-0.096632786:0.02573139], len= 0.1, angle:165.08925

After some debugging, the problems seems to be in this line of code in Arrive.class:

// Acceleration tries to get to the target velocity without exceeding max acceleration
        // Notice that steering.linear and targetVelocity are the same vector
        targetVelocity.sub(owner.getLinearVelocity()).scl(1f / timeToTarget).limit(actualLimiter.getMaxLinearAcceleration());

When timeToTarget has the default value of .1f and the velocity is very small, for example [0.011008702:0.1401149], the resulting vector is to long [0.11008702:1.401149] and it gets limited to the MaxLinearAcceleration.

When i commenting out this code

.scl(1f / timeToTarget)

or setting timeToTarget to 1, everything works fine.

Is this behavior the way it should work? For what do i need the timeToTarget?

Why is the MessageDispatcher's delay in seconds ?

According to the documentation, the MessageDispatcher can delay the message. The delay is always given in seconds.

However, some games could use another piece of time. For example, on turn-based game, time can be expressed in turns, not in seconds. So we could send a message in 3 turns for example.

Is there any reason to force the delay in seconds ? Could gdx-ai provide a more "generic way" to delay a message ?

SteeringBehaviorsTest - flee test addition and option to not wrap around display area

Some small enhancements to the SteeringBehavioursTest suite that could be useful :-)

Would it be possible to add a test for fleeing to the SteeringBehavioursTest?

Also would it be possible to add a toggle to the behaviour of the AI actors to be able to stay within the display area (i.e. not wrap around on hitting the world bounds) and behave appropriately?)

Keep up the good work :-D

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.