1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//1. Declare module name
module ship; 

//2. The ship will need to make use of all these imports, plus we define playerShip class global 
import
    arc.window,
    arc.texture,
    arc.sound,
    arc.input,
    arc.scenegraph.all,
    arc.physics.shapes.circle,
    arc.types;
        
import asteroid;
import laserbolt;

Ship playerShip;

//3. Our ship class, it will be a 'scenegraph sprite' 
class Ship : arc.scenegraph.sprite.Sprite
{
public:
    //4. The max speed the ship can accelerate up to (pixels per ms)
    const float MAX_SPEED = 0.1;
    
    //5. This function is used by the physics system to tell if the ship collides with the asteroid
    void onCollideStart(Body a, Body b)
    {
        if(cast(Asteroid)b.getParent() !is null)
        {
            //6. If the ship does collide with the asteroid, we kill the ship
            kill();
        }
    }
    
    //7. Initialize our ship
    this()
    {
        // A. The frame we define when the ship is not moving 
        stillFrame = new Frame(Texture("astbin/ship/still.png"));

        // B. When the ship is moving, this moveAnimation is shown (checkout move.png to see how animation is set up) 
        // the argument to linear animation are /animation/, /size of anim/, /numframes/, /time lapse between frames/, and /anim-loop style/ 
        moveAnimation = new LinearAnimation(new Frame(Texture("astbin/ship/move.png"), Rect(32,32)), 3, 50,  LinearAnimation.Direction.Circular);

        // C. When the ship dies, this explode animation is shown
        explosionAnimation = new LinearAnimation(new Frame(Texture("astbin/ship/explosion2.png"), Rect(32,32)), 7, 100);

        // D. sound effects 
        explodeSnd = new Sound(new SoundFile("astbin/explosion.wav"));
        laserSnd = new Sound(new SoundFile("astbin/ship/laser.wav"));
        
        // E. Instead of calling process manually on sound effects, we can add them to an auto-process list 
        // and arc.sound.process() will process these sounds automatically
        registerAutoProcessSound(explodeSnd);
        registerAutoProcessSound(laserSnd);
        
        // F. Circle is a physics class: radius is 13 and mass 100
        physics = new Circle(13, 100);
        
        // G. Hook up our collision code to the physics circle
        physics.sigCollideStart.connect(&onCollideStart);

        // H. put ship at center of screen
        physics.translation = Point(arc.window.getWidth / 2, arc.window.getHeight / 2);
        
        // I. call the sprite constructor, give the transform node and the initial state 
        super(physics, stillFrame);
    }
    
    //8. Unregister sounds from the auto-process list 
    ~this()
    {
        unregisterAutoProcessSound(laserSnd);
        unregisterAutoProcessSound(explodeSnd);
    }

    //9. Reset the ship, this is usually done after the ship has been killed
    void reset()
    {
        // start out with the static animation
        state = stillFrame;

        // put at center of screen
        physics.translation.set(arc.window.getWidth / 2, arc.window.getHeight / 2);
        physics.velocity.set(0,0);
        physics.angularVelocity = 0;

        // ship is alive again
        alive = true;

    }

    //10. Accelerate by applying a forward force to the physics system 
    void accelerateForward()
    {
        physics.force += Point.fromPolar(0.01, physics.rotation - PI/2);
    }

    //11. Decelerate
    void accelerateBackward()
    {
        physics.force += Point.fromPolar(0.01, physics.rotation + PI/2);
    }
    
    //12. rotational acceleration to the left
    void rotateLeft()
    {
        physics.angularVelocity = -0.003;
    }

    //13. rotational acceleration to the right 
    void rotateRight()
    {
        physics.angularVelocity = 0.003;
    }
    
    //14. Process ship
    void process()
    {
        processControls();
    }
    
    //15. Process the ships controls
    void processControls()
    {
        // start with no angular velocity 
        physics.angularVelocity = 0;
        
        // keep the ship's speed from going above the max speed 
        if(physics.velocity.lengthSquared > MAX_SPEED*MAX_SPEED)
            physics.velocity = physics.velocity.normaliseCopy() * MAX_SPEED;
        
        // process the ships controls if the ship is alive 
        if(alive)
        {
            if (arc.input.keyDown(ARC_LEFT))
                rotateLeft();
    
            if (arc.input.keyDown(ARC_RIGHT))
                rotateRight();
            
            if (arc.input.keyPressed(ARC_UP) || arc.input.keyPressed(ARC_DOWN))
                state = moveAnimation.start();
            
            if (arc.input.keyUp(ARC_UP) && arc.input.keyUp(ARC_DOWN) && state !is stillFrame)
                state = stillFrame;
    
            // speedup when moving, slow down when not
            if (arc.input.keyDown(ARC_UP))
                accelerateForward();
            if (arc.input.keyDown(ARC_DOWN))
                accelerateBackward(); 

            // fire laser when control is hit
            if (arc.input.keyPressed(ARC_LCTRL))
            {
                // play the laser sound
                laserSnd.play();
                
                // add a new laser bolt to the scenegraph with specified position, angle, and rotation
                LaserBolt.addLaser(
                    physics.translation + Point.fromPolar(32, physics.rotation - PI/2),
                    physics.rotation
                    );
                
            }
        }
    }

    //16. Kill the ship and only if ship is alive 
    void kill()
    {
        if(alive)
        {
            // play explode sound effect
            explodeSnd.stop();
            explodeSnd.play();
            
            // start the exploding animation 
            state = explosionAnimation.start();
            
            // ship is now dead
            alive = false;
        }
    }

    //17. Explode and Laser sound effects
    Sound explodeSnd, laserSnd; 

private:
    //18. Our ships variables, animation frames, physics body, and alive boolean 
    Frame stillFrame;
    LinearAnimation explosionAnimation, moveAnimation;
    Body physics;

    bool alive=true;
}