[M1 Mac, Big Sur 11.6.5]
このChapterのcppファイルは17個です。
左から攻めてくる敵機は大砲の設置に対しA*探索アルゴリズムを使って最短ルートを探します。
ゲームプログラミングには数学の知識とセンスが必須だということがよく分かりました。
私は実用アプリしか書いていませんが、こういう要素もたまには取り入れてみたいです。
[M1 Mac, Big Sur 11.6.5]
このChapterのcppファイルは17個です。
左から攻めてくる敵機は大砲の設置に対しA*探索アルゴリズムを使って最短ルートを探します。
ゲームプログラミングには数学の知識とセンスが必須だということがよく分かりました。
私は実用アプリしか書いていませんが、こういう要素もたまには取り入れてみたいです。
[M1 Mac, Big Sur 11.6.5]
Chapter03ではcppファイルが9個から13個に増えました。火球を発射して小惑星を撃ち落とせます。ただし宇宙船に小惑星がぶつかっても何も起こりません。
もはやプログラミングの勉強というよりゲームの進化に興味が向いています。このプログラムはベクトルなどを駆使して書かれています。アプリの動く背景とかに使えそうです。
Makefileはファイルが増えた分を書き足しただけです。
# コンパイラ設定
COMPILER = clang++
DEBUG = -g
# フラグ設定
CPPFLAGS = $(shell pkg-config sdl2 --cflags --libs) -std=c++11
CPPFLAGS2 = $(shell pkg-config sdl2_image --cflags --libs)
CPPFLAGS3 = $(shell pkg-config glew --cflags --libs)
CPPFLAGS4 = $(shell pkg-config sdl2_mixer --cflags --libs)
CPPFLAGS5 = $(shell pkg-config sdl2_ttf --cflags --libs)
CPPFLAGS6 = $(shell pkg-config soil --cflags --libs)
LDFLAGS = -lc++
# includeパス(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/sdl2/2.0.20/include -I/opt/homebrew/Cellar/sdl2_image/2.0.5/include \
-I/opt/homebrew/Cellar/glew/2.2.0_1/include -I/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/include \
-I/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/include -I/usr/local/include
# ライブラリパス(-l)
LIBRARY_l = -lsdl2 -lsdl2_image -lglew -lsdl2_mixer -lsdl2_ttf -lSOIL
# 優先ライブラリパス(-L)
LIBRARY_L = -L/opt/homebrew/Cellar/sdl2/2.0.20/lib -L/opt/homebrew/Cellar/sdl2_image/2.0.5/lib \
-L/opt/homebrew/Cellar/glew/2.2.0_1/lib -L/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/lib \
-L/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/lib -L/usr/local/lib
# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Main.cpp $(SRCDIR)/Game.cpp $(SRCDIR)/Actor.cpp \
$(SRCDIR)/Asteroid.cpp $(SRCDIR)/CircleComponent.cpp $(SRCDIR)/Component.cpp \
$(SRCDIR)/InputComponent.cpp $(SRCDIR)/Laser.cpp $(SRCDIR)/Math.cpp \
$(SRCDIR)/MoveComponent.cpp $(SRCDIR)/Random.cpp $(SRCDIR)/Ship.cpp \
$(SRCDIR)/SpriteComponent.cpp
# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Main.o $(OBJDIR)/Game.o $(OBJDIR)/Actor.o \
$(OBJDIR)/Asteroid.o $(OBJDIR)/CircleComponent.o $(OBJDIR)/Component.o \
$(OBJDIR)/InputComponent.o $(OBJDIR)/Laser.o $(OBJDIR)/Math.o \
$(OBJDIR)/MoveComponent.o $(OBJDIR)/Random.o $(OBJDIR)/Ship.o \
$(OBJDIR)/SpriteComponent.o
# 実行ファイル
TARGETDIR = ./bin
TARGET = Game03_01
# cppファイルからoファイル作成 $<:依存ファイル
#1
$(OBJDIR)/Main.o: $(SRCDIR)/Main.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#2
$(OBJDIR)/Game.o: $(SRCDIR)/Game.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#3
$(OBJDIR)/Actor.o: $(SRCDIR)/Actor.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#4
$(OBJDIR)/Asteroid.o: $(SRCDIR)/Asteroid.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#5
$(OBJDIR)/CircleComponent.o: $(SRCDIR)/CircleComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#6
$(OBJDIR)/Component.o: $(SRCDIR)/Component.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#7
$(OBJDIR)/InputComponent.o: $(SRCDIR)/InputComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#8
$(OBJDIR)/Laser.o: $(SRCDIR)/Laser.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#9
$(OBJDIR)/Math.o: $(SRCDIR)/Math.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#10
$(OBJDIR)/MoveComponent.o: $(SRCDIR)/MoveComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#11
$(OBJDIR)/Random.o: $(SRCDIR)/Random.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#12
$(OBJDIR)/Ship.o: $(SRCDIR)/Ship.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#13
$(OBJDIR)/SpriteComponent.o: $(SRCDIR)/SpriteComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)
# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(TARGET)
# oファイル・実行ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET) $(TARGET).app
[M1 Mac, Big Sur 11.6.5]
GUIのXCodeは使わずにコンソールでコンパイル&ビルドしています。Chapter01のソースコードは簡単に動いたものの、Chapter02になって難易度が格段に上がりました。
Actor.oが生成されていないことに気が付かず長時間停滞したこともあり、ファイルが見やすいように各ディレクトリに移動させました。
Makefileは後のChapterを視野に入れているので本来必要な内容より分量多めになっています。
またしてもFLTK&OpenCVの時と同様にmath.hがらみのエラーが発生したため、Math.hをMath2.hにリネームしました。FLTKの開発者もそうですが、安易にmath.hという命名はしないでいただきたいものです。
とりあえずサンプルアプリは動いています。WSAD各キーで宇宙船の位置を変えることができます。方向キーに変えた方がプレイしやすいですね。
# コンパイラ設定
COMPILER = clang++
DEBUG = -g
# フラグ設定
CPPFLAGS = $(shell pkg-config sdl2 --cflags --libs) -std=c++11
CPPFLAGS2 = $(shell pkg-config sdl2_image --cflags --libs)
CPPFLAGS3 = $(shell pkg-config glew --cflags --libs)
CPPFLAGS4 = $(shell pkg-config sdl2_mixer --cflags --libs)
CPPFLAGS5 = $(shell pkg-config sdl2_ttf --cflags --libs)
CPPFLAGS6 = $(shell pkg-config soil --cflags --libs)
LDFLAGS = -lc++
# includeパス(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/sdl2/2.0.20/include -I/opt/homebrew/Cellar/sdl2_image/2.0.5/include \
-I/opt/homebrew/Cellar/glew/2.2.0_1/include -I/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/include \
-I/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/include -I/usr/local/include
# ライブラリパス(-l)
LIBRARY_l = -lsdl2 -lsdl2_image -lglew -lsdl2_mixer -lsdl2_ttf -lSOIL
# 優先ライブラリパス(-L)
LIBRARY_L = -L/opt/homebrew/Cellar/sdl2/2.0.20/lib -L/opt/homebrew/Cellar/sdl2_image/2.0.5/lib \
-L/opt/homebrew/Cellar/glew/2.2.0_1/lib -L/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/lib \
-L/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/lib -L/usr/local/lib
# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Main.cpp $(SRCDIR)/Game.cpp $(SRCDIR)/Actor.cpp \
$(SRCDIR)/AnimSpriteComponent.cpp $(SRCDIR)/BGSpriteComponent.cpp $(SRCDIR)/Component.cpp \
$(SRCDIR)/Math.cpp $(SRCDIR)/Ship.cpp $(SRCDIR)/SpriteComponent.cpp
# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Main.o $(OBJDIR)/Game.o $(OBJDIR)/Actor.o \
$(OBJDIR)/AnimSpriteComponent.o $(OBJDIR)/BGSpriteComponent.o $(OBJDIR)/Component.o \
$(OBJDIR)/Math.o $(OBJDIR)/Ship.o $(OBJDIR)/SpriteComponent.o
# 実行ファイル
TARGETDIR = ./bin
TARGET = Game02_01
# cppファイルからoファイル作成 $<:依存ファイル
#1
$(OBJDIR)/Main.o: $(SRCDIR)/Main.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#2
$(OBJDIR)/Actor.o: $(SRCDIR)/Actor.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#3
$(OBJDIR)/Game.o: $(SRCDIR)/Game.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#4
$(OBJDIR)/AnimSpriteComponent.o: $(SRCDIR)/AnimSpriteComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#5
$(OBJDIR)/BGSpriteComponent.o: $(SRCDIR)/BGSpriteComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#6
$(OBJDIR)/Component.o: $(SRCDIR)/Component.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#7
$(OBJDIR)/Math.o: $(SRCDIR)/Math.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#8
$(OBJDIR)/Ship.o: $(SRCDIR)/Ship.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
#9
$(OBJDIR)/SpriteComponent.o: $(SRCDIR)/SpriteComponent.cpp
$(COMPILER) $(CPPFLAGS) $(CPPFLAGS2) $(CPPFLAGS3) $(CPPFLAGS4) $(CPPFLAGS5) $(CPPFLAGS6) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)
# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(TARGET)
# oファイル・実行ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET) $(TARGET).app
[M1 Mac, Big Sur 11.6.5]
苦行のようなFLTKアプリ開発がひと段落したので、しばらくはゆるく過ごします。
『ゲームプログラミングC++』という本を購入しました。サンプルコードをいじりながら、楽しく学んでいきます。
Visual StudioやXCodeを使わないノンIDE派の手作り好きなので、以下のMakefileを走らせてコンパイル&ビルドしています。
# コンパイラ設定
COMPILER = clang++
DEBUG = -g
# フラグ設定
CPPFLAGS = $(shell pkg-config sdl2 --cflags --libs) -std=c++11
LDFLAGS =
# includeパス(-I)
INCLUDE = -I/opt/homebrew/Cellar/sdl2/2.0.20/include
# ライブラリパス(-l)
LIBRARY_l = -lsdl2
# 優先ライブラリパス(-L)
LIBRARY_L = -L/opt/homebrew/Cellar/sdl2/2.0.20/lib
# ソースファイル
SRCDIR = .
SRCS = $(SRCDIR)/Main.cpp $(SRCDIR)/Game.cpp
# オブジェクトファイル
OBJDIR = .
OBJS = $(OBJDIR)/Main.o $(OBJDIR)/Game.o
# 実行ファイル
TARGETDIR = .
TARGET = Game01MOD
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/Main.o: $(SRCDIR)/Main.cpp
$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<
$(OBJDIR)/Game.o: $(SRCDIR)/Game.cpp
$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)
# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(TARGET)
# oファイル&実行ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
これまでSwingを使った簡単なツールの開発過程を記事にしてきました。
今はそれなりの完成度を目指しアプリを開発していますが、基礎的なレベルを離れてネットでもなかなか見つからない独自スキルや創意工夫を盛り込み始めましたので、SwingなどGUI関連は原則的に非公開とします。
そういったことを気に掛ける程度にはプログラミングレベルが向上してきた様です。
スーパークラス型の変数にサブクラスのオブジェクトを代入すると、インスタンスメンバはスーパークラス、インスタンスメソッドはstatic修飾子を付けない限りサブクラスのメンバが呼び出されます。
Amazonのブラックフライデーセールが明日までなので、Goldの紫本も購入しました。来年1月末までにSilver本とGold本を読破したいです。
前にも書きましたがオラクルの検定は受けません。苦痛でしかない練習問題を解く代わりにサンプルコードをいじって色々動作検証しています。その方が楽しいですし、スキルが身に付きやすいと思います。
class Super {
String x = "Super : x";
String y = "Super : y";
void methodA() { System.out.println("Super : methodA()"); }
static void methodB() { System.out.println("Super : methodB()"); }
}
class Sub extends Super {
String x = "Sub : x";
String y = "Sub : y";
void methodA() { System.out.println("Sub : methodA()"); }
static void methodB() { System.out.println("Sub : methodB()"); }
}
public class Main2 {
public static void main(String[] args) {
Super obj = new Sub();
System.out.println(obj.x);
System.out.println(obj.y);
obj.methodA();
obj.methodB();
}
}
--------------------------------------------------
出力
--------------------------------------------------
Super : x
Super : y
Sub : methodA()
Super : methodB()
これまでいくつかJavaコードを書いてきましたが、interfaceは全くの初見でした。
本にはサンプルコードがなかったのでネットから適当に探してきました。
下記サンプルコードではinterfaceを使うメリットがよく分かりません。interfaceを使わない方がスッキリ書けます。開発者が初期値をinterfaceで設定しプログラマがそれに従ってコードを作成するというイメージで覚えることにしました。
以下2つのコードの内容は同じ
--------------------------------------------------
interface Calc {
int NUM1 = 10;
int NUM2 = 50;
void calc();
}
class Add implements Calc {
public void calc() {
System.out.println(NUM1 + NUM2);
}
}
class Sub implements Calc {
public void calc() {
System.out.println(NUM1 - NUM2);
}
}
public class Main {
public static void main(String[] args) {
Add add = new Add();
add.calc();
Sub sub = new Sub();
sub.calc();
}
}
--------------------------------------------------
class Calc {
int NUM1 = 10;
int NUM2 = 50;
public void add() {
System.out.println(NUM1 + NUM2);
}
public void sub() {
System.out.println(NUM1 - NUM2);
}
}
public class Main {
public static void main(String[] args) {
Calc cal = new Calc();
cal.add();
cal.sub();
}
}
--------------------------------------------------
出力
--------------------------------------------------
60
-40
abstractの役割が今一つピンときませんでしたが、具象クラスであるサブクラスを作成する際にオーバーライドを強制する、つまり開発者が設定した抽象クラスをプログラマがサブクラス作成時に必ず記述しなければならない、という説明で理解できました。
全てではないでしょうが開発者の意図通りにコーディングさせる手段の一つという解釈でそんなに外していない様です。
abstract class X { // 抽象クラス
protected abstract void methodA();
}
abstract class Y extends X { } // 抽象クラス
class Z extends Y { // 具象クラス
protected void methodA() { } // 具象クラスではオーバーライドは必須
}
サブクラス実行の際はsuperクラスのコンストラクタはかならず実行されますが、特に明示しない限り引数なしのコンストラクタが呼び出されます。
以下のコードは動作確認のためだけに作成されたものです。意味は分かりますが実用に寄与しない内容でもやもやします。
class SuperA {
public SuperA() { System.out.println("SuperA()"); }
public SuperA(int a) { System.out.println("SuperA(int a)"); }
}
class SubA extends SuperA {
public SubA() { System.out.println("SubA()"); }
public SubA(int a) {
super(a); // 引数付きコンストラクタを呼び出すのに必要な記述
System.out.println("SubA(int a)"); }
}
public class Main {
public static void main(String[] args) {
SubA obj1 = new SubA();
SubA obj2 = new SubA(10);
}
}
--------------------------------------------------
出力
--------------------------------------------------
SuperA()
SubA()
SuperA(int a)
SubA(int a)
サブクラスにおいて同じメソッド名で異なる機能にする場合はオーバーライドのルールに従います。`@Overrideを記述するとメソッド名が違っていないかチェックしてくれます。
オーバーライドを多用するとロクなことにならない様な気がするのですが、大規模開発では必要な機能なのでしょうか。
class SuperA {
public void print(String s) {
System.out.println("SuperA print : " + s);
}
public void method() { }
}
class SubA extends SuperA {
@Override
public void Print(String s) { // printがPrintになっているのでコンパイルエラーになります
s = "渡された文字列は " + s + " です";
System.out.println("SubA print : " + s);
}
}
public class Main {
public static void main(String[] args) {
SuperA obj1 = new SuperA();
obj1.print("Java");
SubA obj2 = new SubA();
obj2.print("Java");
}
}
--------------------------------------------------
出力
--------------------------------------------------
Main.java:8: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません