Ejemplo n.º 1
0
        /**
         * Fired when the GUI event loop "ticks". This provides an opportunity
         * to receive user input as well as send the framebuffer to be rendered.
         */
        private static void GUI_OnTick(GUITickEventArgs eventArgs)
        {
            _game.ButtonP1Left  = eventArgs.ButtonP1Left;
            _game.ButtonP1Right = eventArgs.ButtonP1Right;
            _game.ButtonP1Fire  = eventArgs.ButtonP1Fire;
            _game.ButtonP2Left  = eventArgs.ButtonP2Left;
            _game.ButtonP2Right = eventArgs.ButtonP2Right;
            _game.ButtonP2Fire  = eventArgs.ButtonP2Fire;
            _game.ButtonStart1P = eventArgs.ButtonStart1P;
            _game.ButtonStart2P = eventArgs.ButtonStart2P;
            _game.ButtonCredit  = eventArgs.ButtonCredit;
            _game.ButtonTilt    = eventArgs.ButtonTilt;

            // If the PAUSE key was pressed (e.g. CTRL/CMD+BREAK), invoke the
            // interactive debugger.
            if (_game.Debug && eventArgs.ShouldBreak)
            {
                _game.Break();
            }

            if (_renderFrameNextTick)
            {
                eventArgs.FrameBuffer  = _frameBuffer;
                eventArgs.ShouldRender = true;
                _renderFrameNextTick   = false;
            }

            if (_playSoundNextTick)
            {
                eventArgs.ShouldPlaySounds = true;
                eventArgs.SoundEffects.AddRange(_soundEffects);
                _soundEffects.Clear();
                _playSoundNextTick = false;
            }
        }
Ejemplo n.º 2
0
        private void UpdateKeys(GUITickEventArgs tickEventArgs, SDL.SDL_Keycode keycode, bool isDown)
        {
            switch (keycode)
            {
            case SDL.SDL_Keycode.SDLK_LEFT:
                tickEventArgs.ButtonP1Left = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_RIGHT:
                tickEventArgs.ButtonP1Right = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_SPACE:
                tickEventArgs.ButtonP1Fire = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_a:
                tickEventArgs.ButtonP2Left = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_d:
                tickEventArgs.ButtonP2Right = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_p:
                tickEventArgs.ButtonP2Fire = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_1:
                tickEventArgs.ButtonStart1P = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_2:
                tickEventArgs.ButtonStart2P = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_5:
                tickEventArgs.ButtonCredit = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_t:
                tickEventArgs.ButtonTilt = isDown;
                break;
            }
        }
Ejemplo n.º 3
0
        /**
         * Used to start the GUI event loop using the SDL window to poll for events. When an event is
         * received, the keyboard state is read and the OnTick event is fired. After the event completes
         * a frame and/or audio can be rendered/played based on the values set in the OnTick event args.
         *
         * This is a BLOCKING call; the event loop will continue to be polled until (1) the user uses
         * the platform specific key combination or GUI action to close the window or (2) the OnTick
         * event arguments has ShouldQuit set to true.
         */
        public void StartLoop()
        {
            // Used to keep track of the time elapsed in each loop iteration. This is used to
            // notify the OnTick handlers so they can update their simulation, as well as throttle
            // the update loop to targetTicskHz if needed.
            var stopwatch = new Stopwatch();

            // Structure used to pass data to and from the OnTick handlers. We initialize it once
            // outside of the loop to avoid eating a ton of memory putting GC into a tailspin.
            var tickEventArgs = new GUITickEventArgs();

            // The SDL event polled for in each iteration of the loop.
            SDL.SDL_Event sdlEvent;

            while (true)
            {
                stopwatch.Restart();

                tickEventArgs.KeyDown     = null;
                tickEventArgs.ShouldBreak = false;

                while (SDL.SDL_PollEvent(out sdlEvent) != 0)
                {
                    switch (sdlEvent.type)
                    {
                    // e.g. Command + Q, ALT+F4, or clicking X...
                    case SDL.SDL_EventType.SDL_QUIT:
                        // Break out of the SDL event loop, which will close the program.
                        return;

                    case SDL.SDL_EventType.SDL_KEYDOWN:
                        tickEventArgs.KeyDown = sdlEvent.key.keysym.sym;
                        UpdateKeys(tickEventArgs, sdlEvent.key.keysym.sym, true);

                        // If the break/pause or 9 key is pressed, set a flag indicating the
                        // emulator's should activate the interactive debugger.
                        if (sdlEvent.key.keysym.sym == SDL.SDL_Keycode.SDLK_PAUSE ||
                            sdlEvent.key.keysym.sym == SDL.SDL_Keycode.SDLK_9)
                        {
                            tickEventArgs.ShouldBreak = true;
                        }

                        break;

                    case SDL.SDL_EventType.SDL_KEYUP:
                        UpdateKeys(tickEventArgs, sdlEvent.key.keysym.sym, false);
                        break;
                    }
                }

                // Update the event arguments that will be sent with the event handler.

                tickEventArgs.ShouldRender     = false;
                tickEventArgs.ShouldPlaySounds = false;
                tickEventArgs.SoundEffects.Clear();

                // Delegate out to the event handler so work can be done.
                if (OnTick != null)
                {
                    OnTick(tickEventArgs);
                }

                // We only want to re-render if the frame buffer has changed since last time because
                // the SDL_RenderPresent method is relatively expensive.
                if (tickEventArgs.ShouldRender && tickEventArgs.FrameBuffer != null)
                {
                    // Render screen from the updated the frame buffer.
                    // NOTE: The electron beam scans from left to right, starting in the upper left corner
                    // of the CRT when it is in 4:3 (landscape), which is how the framebuffer is stored.
                    // However, since the CRT in the cabinet is rotated left (-90 degrees) to show the game
                    // in 3:4 (portrait) we need to perform the rotation of points below by starting in the
                    // bottom left corner of the window and drawing upwards, ending on the top right.

                    // Clear the screen.
                    SDL.SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 0);
                    SDL.SDL_RenderClear(_renderer);

                    var bits = new System.Collections.BitArray(tickEventArgs.FrameBuffer);

                    var x = 0;
                    var y = SpaceInvaders.RESOLUTION_WIDTH - 1;

                    for (var i = 0; i < bits.Length; i++)
                    {
                        if (bits[i])
                        {
                            // The CRT is black/white and the framebuffer is 1-bit per pixel.
                            // A transparent overlay added "colors" to areas of the CRT. These
                            // are the approximate y locations of each area/color of the overlay:

                            if (y >= 182 && y <= 223)
                            {
                                SDL.SDL_SetRenderDrawColor(_renderer, 0, 255, 0, 255); // Green - player and shields
                            }
                            else if (y >= 33 && y <= 55)
                            {
                                SDL.SDL_SetRenderDrawColor(_renderer, 255, 0, 0, 255); // Red - UFO
                            }
                            else
                            {
                                SDL.SDL_SetRenderDrawColor(_renderer, 255, 255, 255, 255); // White - Everything else
                            }
                            SDL.SDL_RenderDrawPoint(_renderer, x, y);
                        }

                        y--;

                        if (y == -1)
                        {
                            y = SpaceInvaders.RESOLUTION_WIDTH - 1;
                            x++;
                        }

                        if (x == SpaceInvaders.RESOLUTION_HEIGHT)
                        {
                            break;
                        }
                    }

                    SDL.SDL_RenderPresent(_renderer);
                }

                // Handle playing sound effects.
                if (soundEffects != null &&
                    tickEventArgs.ShouldPlaySounds &&
                    tickEventArgs.SoundEffects != null)
                {
                    foreach (var sfx in tickEventArgs.SoundEffects)
                    {
                        var pointer = soundEffects[sfx];

                        // Result of Mix_PlayChannel, which indicates the channel the sound is playing on.
                        // -1 indicates an error.
                        var playChannelResult = -1;

                        switch (sfx)
                        {
                        // The UFO sound effect loops until the UFO disappears or is destroyed.
                        case SoundEffect.UFO_Start:
                            playChannelResult = SDL_mixer.Mix_PlayChannel(AUDIO_CHANNEL_UFO, pointer, AUDIO_INFINITE_LOOP);
                            break;

                        case SoundEffect.UFO_Stop:
                        {
                            SDL_mixer.Mix_Pause(AUDIO_CHANNEL_UFO);

                            // Mix_Pause doesn't return a channel or error code. Ensure we don't leave as -1.
                            playChannelResult = AUDIO_CHANNEL_UFO;

                            break;
                        }

                        case SoundEffect.InvaderMove1:
                        case SoundEffect.InvaderMove2:
                        case SoundEffect.InvaderMove3:
                        case SoundEffect.InvaderMove4:
                            playChannelResult = SDL_mixer.Mix_PlayChannel(AUDIO_CHANNEL_INVADER_MOVEMENT, pointer, AUDIO_NO_LOOP);
                            break;

                        default:
                            playChannelResult = SDL_mixer.Mix_PlayChannel(AUDIO_CHANNEL_COMMON, pointer, AUDIO_NO_LOOP);
                            break;
                        }

                        if (playChannelResult == -1)
                        {
                            Console.WriteLine("Error playing sound effect {0}. SDL Error: {1}", sfx, SDL.SDL_GetError());
                        }
                    }
                }

                // See if we need to delay to keep locked to ~ targetTicskHz.

                if (stopwatch.Elapsed.TotalMilliseconds < (1000 / _targetTicksHz))
                {
                    var delay = (1000 / _targetTicksHz) - stopwatch.Elapsed.TotalMilliseconds;
                    SDL.SDL_Delay((uint)delay);
                }

                // If the event handler indicated we should quit, then stop.
                if (tickEventArgs.ShouldQuit)
                {
                    return;
                }
            }
        }