Структуры данных для передачи сообщений в рамках программы?

Вот решение «разделяй и властвуй».

Если квадратный корень из натурального числа (number) является натуральным числом (solution), вы можете легко определить диапазон для solution на основе количества цифр в number:

  • number имеет 1 цифру: solution в диапазоне = 1 - 4
  • number имеет 2 цифры: solution в диапазоне = 3 - 10
  • number имеет 3 цифры: solution в диапазоне = 10 - 40
  • number имеет 4 цифры: solution в диапазоне = 30 - 100
  • number имеет 5 цифр: solution в диапазоне = 100 - 400

Заметить повторение?

Вы можете использовать этот диапазон в подходе двоичного поиска, чтобы увидеть, существует ли solution ] для которого:

number == solution * solution

Вот код

Вот мой класс SquareRootChecker

public class SquareRootChecker {

    private long number;
    private long initialLow;
    private long initialHigh;

    public SquareRootChecker(long number) {
        this.number = number;

        initialLow = 1;
        initialHigh = 4;
        if (Long.toString(number).length() % 2 == 0) {
            initialLow = 3;
            initialHigh = 10;
        }
        for (long i = 0; i < Long.toString(number).length() / 2; i++) {
            initialLow *= 10;
            initialHigh *= 10;
        }
        if (Long.toString(number).length() % 2 == 0) {
            initialLow /= 10;
            initialHigh /=10;
        }
    }

    public boolean checkSquareRoot() {
        return findSquareRoot(initialLow, initialHigh, number);
    }

    private boolean findSquareRoot(long low, long high, long number) {
        long check = low + (high - low) / 2;
        if (high >= low) {
            if (number == check * check) {
                return true;
            }
            else if (number < check * check) {
                high = check - 1;
                return findSquareRoot(low, high, number);
            }
            else  {
                low = check + 1;
                return findSquareRoot(low, high, number);
            }
        }
        return false;
    }

}

А вот пример того, как использовать его.

long number =  1234567;
long square = number * number;
SquareRootChecker squareRootChecker = new SquareRootChecker(square);
System.out.println(square + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677489: true"

long notSquare = square + 1;
squareRootChecker = new SquareRootChecker(notSquare);
System.out.println(notSquare + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677490: false"
35
задан Ricket 27 July 2009 в 16:35
поделиться

4 ответа

(Отказ от ответственности: я никогда не программировал игры на Java, только на C ++. Но общая идея должна быть применима и к Java. Идеи, которые я представляю, не являются моими собственными, а представляют собой смесь решений, которые я нашел в книгах или «в Интернете», см. Раздел ссылок. Я сам использую все это, и пока это приводит к чистому дизайну, где я точно знаю, где разместить новые функции, которые я добавляю.)

Боюсь, это будет длинный ответ, он может быть неясным при первом чтении время, так как я не могу описать это просто сверху вниз очень хорошо, поэтому будут ссылки туда и обратно, это из-за моего недостатка навыков объяснения, а не из-за недостатка дизайна. Оглядываясь назад, я переусердствовал и, возможно, даже не по теме. Но теперь, когда я все это написал, я не могу заставить себя просто выбросить это. Просто спросите, если что-то неясно.

Прежде чем приступить к разработке любого из пакетов и классов, начните с анализа. Какие функции вы хотите иметь в игре. Не планируйте "может быть, я добавлю это позже", потому что почти наверняка проектные решения, которые вы принимаете заранее, прежде чем всерьез начнете добавлять эту функцию, заготовки, которую вы запланировали для нее, будет недостаточной.

И для мотивации, я говорю здесь по опыту, не думайте о своих задача как написать игровой движок, написать игру! Что бы вы ни думали о том, что было бы здорово иметь для будущего проекта, отклоняйте это, если вы не добавляете это в игру, которую пишете прямо сейчас. Никакого непроверенного мертвого кода, никаких проблем с мотивацией из-за невозможности решить проблему, которая даже не является проблемой для ближайшего будущего проекта. Идеального дизайна нет, но достаточно хорошего. Об этом стоит помнить.

Как сказано выше, я не верю, что MVC может быть полезен при разработке игры. Разделение модель / представление не является проблемой, а материал контроллера довольно сложен, If you want to have subpackages named model, view, control, go ahead. The following can be integrated into this packaging scheme, though others are at least as sensible.

It is hard to find a starting point into my solution, so I just start top-most:

In the main program, I just create the Application object, init it and start it. The application's init() will create the feature servers (see below) and inits them. Also the first game state is created and pushed on top. (also see below)

Feature servers encapsulate orthogonal game features. These can be implemented independently and are loosely coupled by messages. Example features: Sound, visual representation, collision detection, artificial intelligence/decision making, physics, and so on. How the features themselves are organized is described below.

Input, control flow and the game loop

Game states present a way to organize input control. I usually have a single class that collects input events or capture input state and poll it later (InputServer/InputManager) . If using the event based approach the events are given to the single one registered active game state.

When starting the game this will be the main menu game state. A game state has init/destroy and resume/suspend function. Init() will initialize the game state, in case of the main menu it will show the top most menu level. Resume() will give control to this state, it now takes the input from the InputServer. Suspend() will clear the menu view from the screen and destroy() will free any resources the main menu needs.

GameStates can be stacked, when a user starts the game using the "new game" option, then the MainMenu game state gets suspended and the PlayerControlGameState will be put onto the stack and now receives the input events. This way you can handle input depending on the state of your game. With only one controller active at any given time you simplify control flow enormously.

Input collection is triggered by the game loop. The game loop basically determines the frame time for the current loop, updates feature servers, collects input and updates the game state. The frame time is either given to an update function of each of these or is provided by a Timer singleton. This is the canonical time used to determine time duration since last update call.

Game objects and features

The heart of this design is interaction of game objects and features. As shown above a feature in this sense is a piece of game functionality that can be implemented independently of each other. A game object is anything that interacts with the player or any other game objects in any way. Examples: The player avatar itself is a game object. A torch is a game object, NPCs are game objects as are lighting zones and sound sources or any combination of these.

Traditionally RPG game objects are the top class of some sophisticated class hierarchy, but really this approach is just wrong. Many orthogonal aspects can't be put into a hierarchy and even using interfaces in the end you have to have concrete classes. An item is a game object, a pick-able item is a game object a chest is a container is an item, but making a chest pick-able or not is an either or decision with this approach, as you have to have a single hierarchy. And it gets more complicated when you want to have a talking magic riddle chest that only opens when a riddle is answered. There just is no one all fitting hierarchy.

A better approach is to have just a single game object class and put each orthogonal aspect, which usually is expressed in the class hierarchy, into its own component/feature class. Can the game object hold other items? Then add the ContainerFeature to it, can it talk, add the TalkTargetFeature to it and so on.

In my design a GameObject only has an intrinsic unique id, name and location property, everything else is added as a feature component. Components can be added at run-time through the GameObject interface by calling addComponent(), removeComponent(). So to make it visible add a VisibleComponent, make it make sounds, add an AudableComponent, make it a container, add a ContainerComponent.

The VisibleComponent is important for your question, as this is the class that provides the link between model and view. Not everything needs a view in the classical sense. A trigger zone will not be visible, an ambient sound zone won't either. Only game objects having the VisibleComponent will be visible. Визуальное представление обновляется в основном цикле при обновлении VisibleFeatureServer. Затем он обновляет представление в соответствии с зарегистрированными в нем VisibleComponents. Запрашивает ли он состояние каждого или просто ставит в очередь сообщения, полученные от них, зависит от вашего приложения и базовой библиотеки визуализации.

В моем случае я использую Ogre3D. Здесь, когда VisibleComponent присоединяется к игровому объекту, он создает SceneNode, который присоединяется к графу сцены, а к узлу сцены - Entity (представление трехмерной сетки). Каждое сообщение TransformMessage (см. Ниже) обрабатывается немедленно. Затем VisibleFeatureServer заставляет Ogre3d перерисовать сцену в RenderWindow (по сути, детали, как всегда, более сложные)

Сообщения

Итак, как эти функции, состояния игры и игровые объекты взаимодействуют друг с другом? Via messages. A Message in this design is simply any subclass of the Message class. Each concrete Message can have its own interface that is convenient for its task.

Messages can be sent from one GameObject to other GameObjects, from a GameObject to its components and from FeatureServers to the components they are responsible for.

When a FeatureComponent is created and added to a game object it registers itself to the game object by calling myGameObject.registerMessageHandler(this, MessageID) for every message it wants to receive. It also registers itself to its feature server for every message it wants to receive from there.

If the player tries to talk to a character it has in its focus, then the user will somehow trigger the talk action. E.g.: If the char in focus is a friendly NPC, then by pressing the mouse button the standard interaction is triggered. The target game objects standard action is queried by sending it a GetStandardActionMessage. The target game object receives the message and, starting with first registered one, notifies its feature components that want to know about the message. The first component for this message will then set the standard action to the one that will trigger itself (TalkTargetComponent will set standard action to Talk, which it will receive too first.) and then mark message as consumed. The GameObject will test for consumption and see that it is indeed consumed and return to caller. The now modified message is then evaluated and the resulting action invoked

Yes this example seems complicated but it already is one of the more complicated ones. Others like TransformMessage for notifying about position and orientation change are easier to process. A TransformMassage is interesting to many feature servers. VisualisationServer needs it to update GameObject's visual representation on screen. SoundServer to update 3d sound position and so on.

The advantage of using messages rather than invoking methods should be clear. There is lower coupling between components. When invoking a method the caller needs to know the callee. But by using messages this is completely decoupled. If there is no receiver, then it doesn't matter. Also how the receiver processes the message if at all is not a concern of the caller. Maybe delegates are a good choice here, but Java misses a clean implementation for these and in case of the network game, you need to use some kind of RPC, which has a rather high latency. And low latency is crucial for interactive games.

Persistence and marshalling

This brings us to how to pass messages over the network. By encapsulating GameObject/Feature interaction to messages, we only have to worry about how to pass messages over the network. Ideally you bring messages into a universal form and put them into a UDP package and send it. Receiver unpacks message to a instance of the proper class and channels it to the receiver or broadcasts it, depending on the message. Я не знаю, подходит ли встроенная сериализация Java для этой задачи. Но даже если нет, существует множество библиотек, которые могут это сделать.

GameObjects и компоненты делают свое постоянное состояние доступным через свойства (C ++ не имеет встроенной сериализации). They have an interface similar to a PropertyBag in Java with which their state can be retrieved and restored.

References

  • The Brain Dump: The blog of a professional game developer. Also authors of the open source Nebula engine, a game engine used in commercially successful games. Most of the design I presented here is taken from Nebula's application layer.
  • Noteworthy article on above blog, it lays out the application layer of the engine. Another angle to what I tried to describe above.
  • A lengthy discussion on how to lay out game architecture. Mostly Ogre specific, but general enough to be useful for others too.
  • Another argument for component based designs, with useful references at the bottom.
60
ответ дан 27 November 2019 в 07:03
поделиться

Хотя я не совсем уверен, что MVC хорошо подходит для игрового дизайна, есть некоторые статьи, ознакомьтесь с основами размещения различных элементов игровой логики с использованием архитектуры MVC. Вот краткий справочник, который отвечает на многие из ваших вопросов:

Архитектура игры: Модель-Представление-Контроллер

0
ответ дан 27 November 2019 в 07:03
поделиться

Сделайте еще один ответ на вопрос «MVC считается потенциально опасным в играх». Если ваш 3D-рендеринг - это «вид», а ваш сетевой трафик - «вид», тогда разве в конечном итоге удаленные клиенты не будут рассматривать вид как модель? (Сетевой трафик может выглядеть просто еще одним механизмом просмотра, когда вы его отправляете, но на принимающей стороне это ваша окончательная модель, на которой основана ваша игра.) Сохраняйте MVC там, где он принадлежит - отделение визуального представления от логики.

Как правило, вы хотите работать, отправляя сообщение на сервер и ожидая ответа. Находится ли этот сервер на другом континенте или в том же процессе, не имеет значения, обрабатываете ли вы его таким же образом.

Допустим, пользователь запускает игру и выбирает символ 2. Затем пользователь переходит в координаты (5,2). Затем он говорит в публичном чате «привет!». Затем он выбирает сохранить и выйти.

Будьте проще. MUD использовались для простой отправки команды в виде обычного текста (например, «ВЫБРАТЬ символ2», «ПЕРЕЙТИ НА 5,2», «СКАЗАТЬ привет»), и нет особых причин, по которым вы не могли бы этого сделать, если вам удобно писать синтаксический анализатор текста.

Более структурированной альтернативой была бы отправка простого объекта XML, поскольку я знаю, что вы, парни из Java, любите XML;)

<message>
    <choose-character number='2'/>
</message>


<message>
    <move-character x='5' y='2'/>
</message>

<!--- etc --->

В коммерческих играх мы, как правило, используем двоичную структуру, которая содержит идентификатор типа сообщения а затем произвольная полезная нагрузка с сериализацией для упаковки и распаковки таких сообщений на каждом конце. Однако здесь такая эффективность не понадобится.

2
ответ дан 27 November 2019 в 07:03
поделиться

Я не уверен, что среда MVC подходит для игры, но предполагаю, что вы создаете игровой сервер, например, для MUD или простого MMPROGOOGPRG, и этот код читаемость и возможность обновления важнее для вас, чем чистая производительность.

Это зависит от того, сколько пользователей вы хотите поддерживать одновременно, а также от возможностей вашего игрового сервера. Вы можете начать с текстового ввода-вывода, а затем перейти к двоичному или XML-представлению по мере развития вашего проекта.

У меня наверняка будут другие действия, с другим классом, выполняющим каждую возможную команду.

Ваш фронт -end парсер будет создавать объекты UserAction (фактически подклассы, T расширяет UserAction) из уровня сети / представления-> контроллера. Это позволяет вам изменить работу вашей сети в дальнейшем, не разрушая ваше основное приложение. Вы, вероятно, уже думаете, что можете использовать настраиваемую сериализацию или что-то подобное для сообщений с этими объектами UserAction. Это UserAction будет передаваться в его реализацию UserActionHandler (Command) через Factory или просто проверять поле CommandEnum внутри переключателя. Упомянутый обработчик затем произведет необходимую магию на модели, а контроллер заметит изменения состояния модели и отправит уведомления другим игрокам / представлениям и так далее и тому подобное.

Это UserAction будет передаваться в его реализацию UserActionHandler (Command) через Factory или просто проверять поле CommandEnum в переключателе. Упомянутый обработчик затем произведет необходимую магию на модели, а контроллер заметит изменения состояния модели и отправит уведомления другим игрокам / представлениям и так далее и тому подобное.

Это UserAction будет передаваться в его реализацию UserActionHandler (Command) через Factory или просто проверять поле CommandEnum в переключателе. Упомянутый обработчик затем произведет необходимую магию на модели, а контроллер заметит изменения состояния модели и отправит уведомления другим игрокам / представлениям и так далее и тому подобное.

2
ответ дан 27 November 2019 в 07:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: