# Smol game dev journal
###### tags: `journal`, `開工`
## TODOs of soml-game
> in ncu-fresh smol-game
- Other
- [x] find a better file structure
- [x] add ts
- Asssets
- [x] load manager
- [x] img pack
- GamePlay
~~- [ ] use matter physic~~
- [x] many kind of obstacles
- [x] obstacle factory
- [ ] Input Manager
- [ ] Sound manager
- [ ] fx
- [ ] bgm
- UI
- [x] new Button interface
- [x] popup panel
- Network
- [x] login
- [x] score API in backend
- [x] ajax score
- [ ] more anti-cheat
## 第一周
### 7/5
開始尋找一些適合的 game framework / engine。
#### game engine
- normal geame engine
- > they originally designed to export pc games
> But you still can export webGL or webASM version
- unity
- unreal
- godot
- > maybe I will try this one
- using `gScript` and `C#`
- web game lib
- three.js
- not a good option for game develop (more like a 3D rendering engine)
- phaser
- looks great
- web game engine
- play canvas engine
- looks powerful, but the free version seems limited
- but it also has a no-editor version
- Gdevelop
- > too simple
當然我也可以直接使用 Unity ,不過我想多做一點嘗試的喵。
因此便採用了 `Phaser`。
### 7/6
嘗試將 Phaser import 制 vue 中,一直出問題,有夠頭痛。後來發現是因為自己對 vue 不熟而導致的問題,不能再一開始就 import Phaser,因為 Phaser 會在自己被 import 時在目標元素底下加入子元素 `canvas` 作為遊戲顯示的地方,而在 vue 的初始階段當然不允許你打亂他的 rendering。
- error: `navigator not found`
- [solution blog](https://blog.csdn.net/jsxg2009/article/details/115244629)
- 要在 `mounted` 階段 import Phaser 不然有些東西 vue 不允許 access
- structure
- [solution blog](https://connectshark.github.io/Phaser3/phaserInVue.html)
#### import phaser
```bash=
$ npm install phaser
```
```vue
# index.vue
<template>
<div id='phaser-container'></div>
</template>
<script>
export default {
mounted() {
import('../game').then((module)=> {
// window.Phaser = new MyGame();
window.Phaser = new module.default();
})
},
beforDestroy() {
window.Phaser.destroy();
}
}
</script>
```
```javascript=
// index.js
const config = {};
class MyGame extends Phaser.Game {
constructor() {
super(config);
}
}
export default MyGame;
```
---
### 7/7
我發現,大該是因為 `javascript` 特性的關西,在不同教學中同一種功能有千百種不同寫法,而且都很丑(`this` 滿天飛)
因此如果用 ts 去找,可以找到比較好看的寫法,剩下的只要我再把它用 js 來寫就好了
### 7/8
0708 更: 我好想寫 typescript
## 第二周
因為實在是太丑了,在加上 Phaser 原碼也是用 typescript 寫的便把專案改成 typescript 了。
可以直接裝 nuxt 專用的 typescript modules 裝起來挺簡單的。
將遊戲物件繼承 `Phaser.GameObjects.Sprite` 後基本上寫法就很接近 Unity 了,一樣有 `preUpdate()` 可以 override,有直接的物理系統可以用。
圖片的引入會有一點問題,因為圖片路徑若放在 `/assets` 中會被 Nuxt 的 webpack 處理而導致 path 會無法預測。因此無法直接用固定的 path 去 load 圖片。
- 解法 1: 把圖片放在 `static` 中
- 解法 2: `import img1Url from './img/img1.png'`
- import png,webpack 會將 import 近來的東西變成它後來實際的 url
### 7/13
研究了一下 OAuth2.0
- [是時候研究一下 OAuth 2.0](https://hackmd.io/5fjSLE8zQxiM1EIV4y9a7A)
### 7/14
找了很久很久,沒有一個好的 UI 系統。嘗試 import 一些 plugin 也一直失敗,便決定自己寫一套。
首先先從 Button 下手...
然後弄的一團糟,晚點再思考如何處理。
#### container
phaser 沒辦法直接給 gameobject 上子 object
但是可以透過 container 來做到
- [container button with rxJS](https://blog.ourcade.co/posts/2020/container-button-phaser-3-typescript-rxjs/)
- container 內的 object 座標位置會變成相對於 container
```typescript=
class Panel extens Pahser.GameObject.Container {
...
}
let panelA = new Panel(...);
let buttonA = new Button(...);
panelA.add(buttonA);
```
#### Button
我想要讓 同一種 behavior 可以加在各個 button 上,並且多出 toggle event
像是:
```typescript=
let buttonA = new Button({x:100, ...});
buttonA.addBehavior(new ImgShowOnHoverBehavior(img: 'thonk'));
buttonA.addBehavior(new TintOnHoverBehavior(tintColor: 0x0c8763));
buttonA.addButtonEventListener(Button.Events.ON_TOGGLE_ON, () => {
turnOnMusic();
})
```
solution code here
##### Approach 1: :heavy_multiplication_x:
```typescript=
abstract class ClickAbleComponent {..}
class Button extends ClickAbleComponent {...}
abstract ButtonDecorator extends ClickAbleComponent {...}
class ShowImgOnMouseHover extends ButtonDecorator {
private img;
constructor(Button button) {
button.container.add(img);
button.addButtonListner(Button.Events.ON_HOVER_ENTER, this.handler);
}
private handler() {...}
}
let button1 = new Button(...);
ShowImgOnMouserHover(button1, imgKey, ...);
```
:thinking_face: well, not really good...
想嘗試弄成像是 decorator pattern 的感覺,但是
event 的 emit 這樣用感覺怪怪的
##### Approach 2: Button has list of behavior :heavy_multiplication_x:
```typescript=
interface IButtonBehavior {...}
interface IButtonBehaviorConfig {
create: (Button but, config: any): IButtonBehavior;
config: any
}
function AddImgShowOnHover(Button but, config): IButtonBehavior{
let behavior = function() {} as IButtonBehavior;
behavior.img = but.scene.add.image(...);
behavior.onMouseHover = function() {
this.img.setVisiable(true);
}
return behavior;
}
class Button {
private container;
private buttonBehaviors;
constructor(behaviorConfig) {
this.buttonBehaviors.push(
behaviorConfig.create(this, beaviorConfig.config));
}
private HandleMouseHover() {
for(behavior in this.buttonBehaviors) {
if (behavior.onMouseHover == function) {
behavior.onMouseHover();
}
}
}
}
let but = new Button(
{
create: AddImgShowOnHover,
config: {
imgKey: 'c8763'
}
},{
create: AddTintOnHover,
config: {
tintColor: 0x0c8763
}
}
);
```
嘗試將 behavior 傳入,但是這樣好像有點冗。 code 變得很不好寫
##### Appraoch 3 :O:
把 Appraoch 2 加上 `init(but, scene, container)` 這樣我就不用一定要在 constructor 裡做將一些元件 add 進 button.container 裡
如此一來我只需要做很多不同 ButtonBehavior,在視情況選擇幾個插入我的 button 中就好。
```typescript=
interface IButtonBehavior {
init: (Button but): void;
onMouseHover: (): void;
...
}
class ShowImgOnHover implements IButtonBehavior {
private img;
constructor(imgKey, pos) {
this.img = new Image(imgKey, pos);
}
// add class members to contaien and scene
init(Button but) {
but.scene.addExisting(this.img);
but.container.add(this.img);
}
// well, dont't use it outside the Button
public onMouseHover() {...}
}
class Button {
private container;
private buttonBehaviors;
constructor(list[IButtonBehavior]) {
for (behavior in list) {
behavior.init(this);
this.buttonBehaviors.push(behavior);
}
}
public addBehavior(IButtonBehavior): this {
}
private HandleMouseHover() {
for(behavior in this.buttonBehaviors) {
if (behavior.onMouseHover == function) {
behavior.onMouseHover();
}
}
}
}
let but = newButton();
but.addBehavior(new ShowImgOnHover(0, 0, 'c8763'))
.addBehavior(new TintOnHover(0x0c8763))
.addBehavior(new addText(0, 0, '28px Arial'));
```
this one seems OK.
實際做起來後看起來跟上面差滿多的但是總體來說差不多。
### 7/15
嘗試讓網站 work,導入了 docker
## 第三周
逐步完整 button.ts 的內容
同時改進 角色 移動物理
加入 scene transition
#### 7/21
發現一些可以對 img 做優惠的 module,叫做 `@aceforth/nuxt-optimized-images`
是把各種 image optimize 的方法集成在一起同時包裝成可以在 nuxt 中方便做設定的 module
嘗試後,確實可以增進一點點圖片載入速度
## 第四周
### 7/26
我想要遊戲內生成不同的 Obstacle
=> `ObstacleFacotry`
:::spoiler 思考草稿
#### solution 1
```typescript=
class ObstacleFactory {
create(currentObj: Obstacle) {
currentObj.activate(true);
let deadZoneAfterCurrent =
currentObj.getDeadZone(this.globalData.worldSpeed).back;
let nextObj = this.getRandomObstacle();
let deadZoneBeforeNext =
nextObj.getDeadZone(this.globalData.worldSpeed).front;
let totalWaitTime = deadZoneAfterCurrent
+ this.getFreeSpace()
+ deadZoneBeforeNext;
if (this.gachaForItem()) {
// create one; above current obstacle or in the freeZone
let item = this.createItem();
if (this.isThisItemAboveObstacle(item, currentObj)) {
} else {
}
}
// obj1 <-obj1.deadZone.back-> <-freeZone-> <-obj2.deadZone.front-> obj2
this.scene.time.addEvent({
delay: totalWaitTime,
callback: this.create
args: [nextObj]
});
return;
}
getRandomObstacle() {...}
}
class GameScene extends Phaser.Scene {
public gobalStatus = {
score: 0,
speed: initSpeed,
}
create() {
this.factory = new ObstacleFactory({
scene: this,
globalData: this.globalData,
});
this.factory.start();
}
}
```
```typescript=
// How to use Obstacle
class Obstacle extends Phaster.GameObject.... {
}
class ObstacleFactory {
obstacleList = [
{val: obstacle1, weight: 0.1}.
{....}
]
itemList = [
{val: item1, weight: 0.1}.
...
]
initConfig = {
x: 1600,
y: ground,
}
getRandomObstacle() {
// how do I
}
}
const createNormalObstacle = () => {
return new normalObstacle1({
// some config
})
}
class normalObstacle1 extends Obstacle {
}
```
:::
</br>
重新架構 obstacle.ts 的樣貌。將 obstacle 改成 abstract class,與 player 的交互行為由子類別來實現。
```typescript=
abstract Obstacle extends Phaser.GameObjects.Sprite {
protect abstract onOverlap(self: Obstacle, player: Player): void;
}
Coin extends Obstacle {
protect onOverlap(self, player) {...}
}
```
### 7/28
img-optimization 的東西有部份是 npm install 會做 build,產出 binary file,而這讓 docker container 內部環境罩成我不知道啥的影響導致 npm start 不起來...
所以 dev 版暫時將 img-optimization 關掉了
跟學校的人 demo
### 7/30
美術的圖片大量產出中,如果要一張一張圖片手動 import 我真的會很想死。
因此便使用 texture packer,製造 sprite sheet,可以一次 import
UI 改成以 sub-scene 的方式 overlap 在 GameScene 上面,由於都是 scene 便可以用 Phaser.scene 的 method 來操作 UI 狀態 (`pause`, `stop`, `lauch`)
### 7/31
> ~~tribes of midgard 滿好玩的,不過很快就有點疲勞了。~~
## 第五周
### 8/2
新的美術圖如果障礙物用方形 collider 會有明顯的怪異感。看來是時候要導入 matter.js 了。
對整體做一些微調
### 8/3
今天不想工作便來整理筆記
對於 UI panel 終於有新的想法了等等可以記起來
有 grid 應該就不用 row 與 column 了
```graphviz
digraph {
node [shape="record"]
com [label="{UIComponent| setPos()}"]
panel [label="{Panel| layout: LayoutSetter\n addOne(component)}"]
button [label="{Button| }"]
butGroup [label="{ButtonGroup| }"]
grid [label="{GridLayout| }"]
row [label="{RowLayout| }"]
lay [label="{LayoutSetter| set(Array\<UIComponent\>): UIComponent}"]
panel -> com [label="implement"]
panel -> lay [label="has"]
button -> com
grid -> lay [label="implement"]
row -> lay
butGroup -> panel [label="extend"]
}
```
> 8/4 updated
```typescript=
// imagenations
let butGroup = new ButtonGroup({x, y, image, behavior, '3x1',});
butGroup.at(0).addEventHandler(event, () => {...})
butGroup.at(1).addEventHandler(event, () => {...})
let panel = new LayoutSetter('1x2', padding: 12);
panel.at(0).add(img, text, ...);
panel.at(1).add(butGroup);
```
```htmlembedded=
// the result will be like this
<div>
<div id='panel'>
<img/>
<text></text>
</div>
<div id='butGroup'>
<button></button>
<button></button>
</div>
</div>
```
```
/UI
/button
...
/panel
basePanel.ts
ButtonGroup.ts
/layoutSetter
GridSetter.ts
UIComponent.ts
type.d.ts
```
```typescript=
interface UIComponent {
margin;
getWidth; // include margin
getHeight;
getCoor(anchor);
}
interface LayoutSetter {
container: Phaser.container;
paddingX; paddingY;
addOne(UIComponent); addMany([UIComponent]);
}
class Panel implements UIComponent {
layoutSetter;
constructor(layoutSetter) {...}
addOne() { this.layoutSetter.addOne(); }
}
class Grid implements LayoutSetter {...}
// init many Button with same config and then align them with a layoutSetter
class ButtonGroup extends Panel {
constructor(numbersOfBut, butConfig, layOutSetter) {}
}
```
研究,參考了一下各種 UI 的寫法,最後採以類似 Unity 中 UI 的架構:
UIComponent 中有 layout 相關設定可以設定子物件的排列。
不過為了配合 Phaser 的 container 設計,便多做一個 panel 物件作為成載多個子物件集合的載體
> 好想打遊戲
> apex season 10 更新了耶
### 8/5
終於寫完新的 UI 功能了,接下來就是把遊戲中暫時用的 UI 換掉了。
好像離 deadline 有點近了,感覺要做不完了 QQ
到底為什麼把東西放到 container 內後,東西的座標會變成以中心...
解法:自己做一個 setPos 會自帶 -0.5
### 8/6
我到底為什麼一直在寫輪子
### 8/7
對 scene 做分檔,因該不用再多做一個 scene manager 了,直接個別做 import 等事就好。
弄好 scene 芬黨後,就直接硬幹 UI
用自己的工具寫 UI 開心 :)
TODO:
GameDataManager 這邊有全部 player data
.item {
unlocked?
amount?
}
.highestScore
.leaderboard
### 8/8
> 問題:UIButton.Button 是基於 phaser.zone 來做點擊判定而非圖片本身,因此在 button 圖片 被其他圖片蓋過去時, button 的 hitArea 仍然可以判定,因此導致 pointer click through the image
> note: img.setInteractive() 可以蓋掉 phaser.zone 的 hitarea 判定
> 因此我需要在一些圖片上加上 setInteractive 來避免 pointer 穿過圖片抵達我的 button.hitArea
Quiz 中的 option button 需要一個新的 layoutSetter
我想要讓 QuizUI 有健全一點的規劃,而不是全部塞在一起
Or maybe just code everything in same scene class is not that bad?
```typescript=
class QuizUI: Phaser.Scene {
constructor() {
super(key);
}
create() {
this.quizManager = new QuizManager;
this.itemBar = new ItemBar(this.quizManager, timer, quizPanel); // get item amout from GameDataManager
this.timer = new Timer(this.quizManager)
this.quizPanel = new Quiz(this.quizManager)
let mainPanel = lineUp(timer, quiz.question, quiz.options);
lineUp(itemBar, mainPanel);
}
}
class QuizManager() {
quizScore: number;
getRandomQuiz() {}
correctAction() {}
wrongAction() {}
}
class ItemBar {
bar: AlignPanel;
constructor() {
this.itemStatus = GameDataManager.itemStatus;
// disabel some button if item.amout <= 0
}
private itemTimeUp() {
// need timer
this.timer.increseTime()
}
private itemGetRidOfWrongOption() {
// need refresh quizPanl
this.quizPanel.eliminateOneWrongOption();
}
private itemScoreX2() {
// need the owner of correctAtion()
this.quizManager.setScoreX2();
}
private itemChangeQuiz() {
// need getRandomQuiz and refresh quizPanel
this.quizPanel.changeQuiz();
}
}
```
### 8/11
QuizUI 越塞越丑了,但是我不想管了
差不多該來設計後端以及資料庫了
> 跟其他人的 db 有要做關聯嗎?
> 算了還是不要,感覺就很麻煩
>
```typescript=
// backend
PlayerModel
player {
user: User; // reference to UserModel Maybe?
studentNumber: number, // main key
nickname: string,
highestScore: number,
itemAmount: {
moreTime: number,
showWorngAns: number,
x2Score: number,
chnageQuiz: number,
},
gameCompeletd: booleam
}
```
`GameDataManager` 目前越來越亂了
或許各資料個體分開有自己的 logic 會比較方便
將 `GameDataManager` 改名為 `GlobalDataManager`,其中存了各種 `xxDataManager`
### 8/12
我覺的我要完蛋了
先把 道具,高分,網路紀錄部份完成
再來是血量顯示,多種障礙物,角色跑步動畫
```typescript=
GlobalDataManager {
}
GameWorldDataManger {
startGame()
pauseGame()
stopGame()
gainScoreAnima
gainScore
updatePlayerData() {
PlayerDataMager.update
}
...
}
PlayserDataManager {
get
save
update
}
asd
QuizDataManager {
}ao6
```
### 8/13
意外的大家分數很低,所以 `backend secuority` 就先不加
先做: 手機板支援(敷衍一下就好,認真做起來很麻煩,排版什麼的
再來:排行榜 -> random-pause-text -> rule-man -> set-nick-name -> backend-sec
### 8/14
休息一天
### 8/15
先來修排行榜,我挺想加入 nick name 的
加入 wasm
### 8/16
C 的 wasm 好難弄,一直失敗
改用 rust 看看
[rust,wasm](https://dev.to/krzysztofkaczy9/webassembly-rust-typescript-project-setup-4gio)
rust 成功了!
Rust Goooooooood!!!!
我要加入 Rust 教了
後來證實 wasm 會影響 webpack 然後影響其他分頁,導致奇怪的 bug
### 休息一週
被 wasm 弄到心態炸裂
找不到任何有關 nuxt+wasm
wasm 又好像有不同種
webpack 也有不同種
再加上還有可能有版本問題
### 9/2
- **被要求要有「累計分數」**
有點嚇到了,不過程式改起來應該很快
把之前的 code 用 cherry pick merge 到 master 留下 wasm 的部分不 merge
> (還好之前有開始養成一個 commit 只改一個東西的習慣,wasm commit 內只有 wasm 相關的修改)
如此就先把 wasm 問題放一旁,先暫時用爛爛的仿偽吧
只要一點簡單的修改就可以把現成的分數計算機制加上「累計分數」制了。
問題在於:已經玩一段時間的玩家怎麼辦
-> 寫一個腳本插到 server 裡直接掰一個「累計分數」給所有人...