實戰Flutter程式開發 - Roulette Wheel Selection

更新於 發佈於 閱讀時間約 40 分鐘
  輪盤賭選擇 ( Roulette Wheel Selection ) 策略是最基本的選擇策略之一,在群體中的個體被選中的概率與個體相應的適應度函式的值成正比,且此選擇方式常出現在賭場,而此範例程式主要是藉由下列三個部份來實作類似輪盤賭選擇 ( Roulette Wheel Selection )的功能。 
  1.   繪製輪盤與指針畫面   
  2.   建立隨機的指針旋轉動畫       
  3.   動態顯示選擇結果

1.   繪製輪盤與指針畫面  

  主要是藉由繼承 (extends) CustomePainter 類別建立新的類別並透過 Canvas 內部封裝繪製圖形的 API 分別建立指針與輪盤組件。

  1-1.  建立指針組件      

  在繪製指針前先使用畫布 (Canvas) 所提供的旋轉 (rotate) API 調整角度後;接著再使用 Path 類別來繪製指針的形狀,由於指針底部是圓弧的形狀因此在使用畫弧 (arcTo) API 時需要建立矩形 (Rect)物件用來定義所畫的圓弧範圍並使用 Paint 類別將繪製指針進行漸層式的顏色渲染。
class DrawPointer extends CustomPainter {
  ...
  @override
  void paint(Canvas canvas, Size size) { 
    // 1. Set the starting position of the drawing graphics on the canvas.
    centerOffst = Offset(size.width / 2, size.height / 2);   
    canvas.translate(centerOffst.dx, centerOffst.dy);
    // 2. The angle by which the pointer is rotated before drawing.
    canvas.rotate(getRadianFromAngle(angle));
    // 3. Crate a rectangle object to define the range of the drawing arc.
    centerSize = Size(size.width / 10, size.height / 5);
    var rect = Rect.fromLTWH(0 - centerSize.width / 2, 0 - centerSize.width / 2, centerSize.width, centerSize.width); 
  
    // 4. Use Path object and Paint object to draw the shape and color of pointer
    canvas.drawPath(
      Path()
        ..arcTo(rect, getRadianFromAngle(30), getRadianFromAngle(300), false)
        ..relativeLineTo(centerSize.height, centerSize.width / 30)
        ..relativeLineTo(0, - 10 * centerSize.width / 30)
        ..relativeLineTo( centerSize.width, 17 * centerSize.width / 30)
        ..relativeLineTo(-centerSize.width, 17 * centerSize.width / 30 )
        ..relativeLineTo(0, -10 * centerSize.width / 30)
        ..close(),
      Paint()
        ..isAntiAlias = true
        ..style = PaintingStyle.fill
        ..shader = LinearGradient(
          begin: Alignment.bottomRight, 
          end: Alignment.topLeft,
          colors:  [Colors.blueGrey.withAlpha(200),  Colors.cyan.withAlpha(200)],
          tileMode: TileMode.mirror,
        ).createShader(rect),
    );
    
    canvas.drawCircle(Offset.zero,  
          centerSize.width * 0.2, 
          Paint()
            ..color=Colors.black
            ..style = PaintingStyle.fill
        );
    canvas.drawCircle(Offset.zero,
          centerSize.width * 0.15, 
          Paint()
            ..color=Colors.red
            ..style = PaintingStyle.fill
        );   
  }
}

  1-2.  建立輪盤組件    

    1-2.1. 根據項目比例繪製扇形圖  
    在繪製輪盤前先使用建立矩形 (Rect)物件用來定義所畫圓的範圍與畫布(Canvas) 中的位置後再依據每個項目比例使用畫布 (Canvas) 所提供繪製圓弧 (drawArc) 的API繪製等比例的扇形使用 Paint 類別進行項目顏色的渲染;另外為了將各項目名稱放置相對應扇形圖的中間位置,因此在繪製扇形圖時利用串列 (List) 類別來儲存繪製扇形的起始弧度與掃角弧度。
void drawRouletteWheel(Canvas canvas, Size size) {
  double startAngle = 0, sweepAngle = 0;   
  // 1. Create a rectagle object to define the range and positio of the drawing pie chart.
  Rect rect = Offset(wheelRectStartPointX, wheelRectStartPointY) & Size(diameter, diameter);
 
  for(int repeat = 0; repeat < itemRepeat; repeat++){
    itemPercent.keys.toList().forEach((element) {
      defaultPaint.color = itemColor.containsKey(element) ?  itemColor[element]!:Colors.white; 
      // 2. Draw an equal-scale pie chart according to the project scale.
      sweepAngle = 360 * itemPercent[element]! / itemRepeat;
      canvas.drawArc(rect, getRadianFromAngle(startAngle),  getRadianFromAngle(sweepAngle), true, defaultPaint);
        
      // 3. Use List class to store the start and the end radian of each sector.
      disPercent.add(WheelTextPosition(
        title:element, 
        startRadian:getRadianFromAngle(startAngle), 
        endRadian:getRadianFromAngle(sweepAngle)
      )); 
        
      startAngle = startAngle + sweepAngle;
     
    });
  }
}
    1-2.2. 根據扇形的位置繪製項目名稱
    由於每個項目文字列印長度與相應扇形的弧長並非相同,因此根據計算項目文字所需的弧長與相應扇形圖的弧長之間的差值來調整項目字串列印的起始位置與角度後,再依序將項目文字中的每個字元依序列印到指定的位置中;另外文字的起始角度為90度,因此在計算列印文字的弧度後需要再加上90度的弧度才是實際字元列印的位置與角度。
void drawWheelText(Canvas canvas, List<WheelTextPosition> disText){
  
  // 1. Set the starting position of the title at an angle of 0 degrees.
  canvas.translate(wheelRectStartPointX + radius , wheelRectStartPointY + radius);
  double radian = 0.0;
  for (var element in disText) {
    // 2. calculate the starting radian of the title on the side of the wheel
    radian = calculateStartRadianForString(element);
    // 3. Add the calculated radian by 90 degrees 
    radian += textRotateRadian;
    for (int i = 0; i< element.title.length; i++){
      canvas.save();
      
      // 4. Rotate each character of the title in order according to the starting radian. 
      radian = drawWheelCharWithAngle(canvas,  element.title[i], radian, lastradian );
      
      canvas.restore();
    }
  }
}
    藉由 TextPainter 類別取得列印字串所需的長度並除以半徑得到 sin θ後再使用 asin 函式轉換成弧度。
 double calculateStartRadianForString( WheelTextPosition whellText ) {  
    
  // 1. Generate title length.
  var _textPainter = TextPainter(
        textDirection: TextDirection.ltr,
        text: TextSpan(text: whellText.title, style:textStyle,),
      );
  _textPainter.layout(minWidth: 0,  maxWidth: double.maxFinite);
 
  // 2. Caculate arc length based on ratio of title to raidus.
  double stringWidthForRadian =  math.asin( _textPainter.width / raduis); 
  
  return whellText.startRadian + (whellText.sweepRadian - stringWidthForRadian) / 2 ;
 
}
    藉由 TextPainter 類別取得列印字元所需的高度並加上半徑後作為列印字元的長度並搭配 sin cos 函式計算其列印字元的座標。另外使用TextPainter 類別取得列印字串所需的長度並除以半徑得到 sin θ 後再使用 asin 函式轉換成弧度。
double drawWheelCharWithAngle(Canvas canvas, String disChar, double curRadian){
 
  // 1. Generate character length.
  var _textPainter = TextPainter(
    textDirection: TextDirection.ltr,
    text: TextSpan(text: disChar, style:textStyle,),
  );    
  _textPainter.layout(minWidth: 0,  maxWidth: double.maxFinite);
  // 2. Calculate character print position.
  double radiusWithHeight = radius +_textPainter.height;
  double shiftPointX = radiusWithHeight * math.sin(curRadian);  // sin(90):1
  double shiftPointY = radiusWithHeight * math.cos(curRadian) ; // cos(90):0
  // 3. adjust the print starting position of the canvas.
  canvas.translate(shiftPointX,  -shiftPointY);
  // 4. Caculate radian based on ratio of character to raidus.  
  curRadian = (curRadian +  math.asin( _textPainter.width / radius));
  
  // 5. Rotate the canvas by arc
  canvas.rotate(curRadian);
  _textPainter.paint(canvas, Offset.zero);
  return curRadian;
}

2.  建立隨機的指針旋轉動畫     

    為了讓指針組件能夠產生旋轉的動畫,因此需要建立可變組件 (State fullWidget) 並混入 (with) TickerProviderStateMixin 類別來建立包含計時器 (TickerProvider) 的可變狀態組件並運用 AnimationController 類別進行動畫控制及使用 CurvedAnimation 與 Tween 類別來定義旋轉動畫的行為;最後使用 Gesture Detector 類別來接收使用者點擊畫面中的位置並判斷點擊位置是否在指針的圓弧位置。
class _WheelPointerState extends State<WheelPointerWidget> with TickerProviderStateMixin {
  Animation<double>? _animation;
  AnimationController? _controller;
  Tween<double>? _rotationTween;   
  int pos = 0;
  bool isSelection = false; 
  
  @override
  void initState() {
    super.initState(); 
    _controller = AnimationController(vsync: this, duration: widget.duration); 
    _animation = CurvedAnimation(parent: _controller!, curve: Curves.fastLinearToSlowEaseIn);
    _rotationTween = Tween<double>(begin:  0, end: 0);
    _animation = _rotationTween!.animate(_animation!)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed){
          isSelection = false;
       
          // 1. Notify of selected radians when animation is done.
          widget.notifyCallbackFunc(isSelection, getRadianFromAngle(pos.toDouble()));
      }
    });
  }
 
  @override 
  void didUpdateWidget (WheelPointerWidget oldWidget){
    super.didUpdateWidget(oldWidget);
    _controller!.duration = widget.duration;    
  }
  @override
  void dispose() {
    _controller!.dispose();
    super.dispose();
  }
  
  void startAnimation() {    
    isSelection = true;
    widget.notifyCallbackFunc(isSelection, 0);
    _controller!.reset();
    // 2. Set the last end of angle as the current starting angle.
    _rotationTween!.begin = pos.toDouble();
    // 3. Generate a random angle from 0 to 360 degrees.  
    pos = math.Random().nextInt(360);
    // 4. Multiply the preset number of rotations by 360 plus a random value.
    _rotationTween!.end = widget.numberOfRevolutions * 360.0 + pos;
     
    // 5. Starts running this animation forward 
    _controller!.forward(); 
  }
  @override 
  Widget build(BuildContext context) {   
    var drawPointer = DrawPointer(_animation!.value);
    return GestureDetector( 
      child: CustomPaint(
        size: widget.canvasSize,
        painter: drawPointer
      ),
      onTapDown: (tapDownDetails) {
       
        // 6. if the animations has been completed and determine the click position.
        if(!isSelection && drawPointer.isclick(tapDownDetails.localPosition)){               
          startAnimation();                
        }
      },
    );
  }
}

3.  動態顯示選擇結果

    最後在指針動畫結束時需要依據指針的角度換算成弧度並以此確認選擇項目後顯示項目名稱,但由於完成上述行為需要將各組件態進行跨組件因此可使用 provider 套件來簡化實作流程。  

 3-1.  建立跨組件的共享數據類別

    首先建立一個可保存跨組件的共享數據類別,此類別是藉由繼承 (extends) InheritedWidget 類別而建立的 ShareDataInheritedWidget 類別,目的是實踐一個可由上至下的共享數據物件且由於具體保存數據類型不可預期,為了通用性則使用泛型的方式來定義此類別。
// 1. Build a generic InheritedWidget to hold state that needs to be shared across components.
class ShareDataInheritedWidget<T> extends InheritedWidget {
  const ShareDataInheritedWidget({
        Key? key,
        required this.prvObj,
        required Widget child
  }) : super(key: key, child: child);
  final T prvObj;
  // 2. Define a convenience method to facilitate the widgets in the subtree to obtain shared data.
  static ShareDataInheritedWidget? of<T>(BuildContext context){
    final ShareDataInheritedWidget? inheritedObj = context.dependOnInheritedWidgetOfExactType<ShareDataInheritedWidget<T>>();
    assert(inheritedObj != null, "No this object found in context!");
    return inheritedObj;
  }
  @override
  bool updateShouldNotify(ShareDataInheritedWidget<T> oldWidget) {
    // 3. This callback determines whether to notify the datadependent widgets in the subtree to rebuild when the data changes.
    return  prvObj != oldWidget.prvObj;
  }
}

  

  3-2.  建立跨組件的事件通知類別

     由於數據發生變化的時候需要重新建構 ShareDataInherited Widget 類別因此需要建立一個共享狀態的類別,此類別是藉由繼承 (extends) ChangeNotifier 類別而建立的 ChangeDataNotifier 類別,其目的是在共享狀態變更時能使用notifyListeners函數來通知註冊此事件的 ShareDataInherited Widget 類別進而實踐跨組件 (Widget) 的事件通知行為且由於具體回傳函數類型不可預期,為了通用性則使用泛型的方式來定義此類別。 
class ChangeDataNotify<T> extends ChangeNotifier {  
  ChangeDataNotify(this.hookData);
  late T hookData;
  void changeDataFunc(T changeData ){
    hookData = changeData;
    // 1. Notify the listeners to rebuild the update state of the InheritedWidget.
    notifyListeners();
  }
  T getDataFunc(){
    return hookData;
  }
}

   

  3-3.  建立 RouletteWheelWidget 組件

    使用前述的類別分別建立 DisplayTitleWidget 組件並搭配使用Consumer 組件用來即時顯示選擇項目和藉由整合輪盤組件和指針組件並搭配使用 provider 套件中的Provider.of 組件用來實現選擇項目更新的即時通知,最後整合建立 RouletteWheelWidget 組件。
class DisplayTitleWidget extends StatelessWidget {
  const DisplayTitleWidget ({Key? key}):super(key: key);
  @override 
  Widget build(BuildContext context) {
    // 1. ChangeDataNotify is provided to widgets in our app through the ChangeNotifierProvider declaration at the top. 
    return Consumer<ChangeDataNotify<String>>(
      builder: (context, obj, child) {
        // 2.  Use ChangeDataNotify widget  here, without rebuilding every time.
        return Text(obj.getDataFunc(), 
          style: const TextStyle(
            color: Colors.brown, 
            fontSize: 24, 
            fontWeight: FontWeight.normal,
            fontStyle: FontStyle.normal,
            decoration: TextDecoration.none,
          ),
        );
      }
    );
  }
}
class RouletteWheelWidget extends StatefulWidget  {
  const RouletteWheelWidget({
    Key? key, 
    required this.radius, 
    required this.textStyle,
    required this.itemRepeat,
    required this.itemPercent, 
    required this.itemColor,    
  }) : super(key: key);
  final double radius;
  final TextStyle textStyle;
  final int itemRepeat;   
  final Map<String, double> itemPercent;
  final Map<String, Color> itemColor;
  @override 
  State<RouletteWheelWidget> createState() => _RouletteWheelState();
}
class _RouletteWheelState extends State<RouletteWheelWidget> {
  // 1. Create uniquely identify elements for access by other object associated with elements.
  final GlobalKey <WheelWidgetState> _wheelWidgetKey = GlobalKey();
  
  WheelWidget? wheelWidget;
  String title = "未知"; 
  void notifyResultFunc(bool isSelection ,double radian) { 
    title = isSelection ? "選擇中" : _wheelWidgetKey.currentState!.getTitleFromRadian(radian);
    // 2. Use Provider.of with the listen parameter set to false without rebuild ChangeDataNotify<String> wedget.
    Provider.of<ChangeDataNotify<String>>(context, listen: false).changeDataFunc(title);   
  }
  @override
  Widget build(BuildContext context) {
    
    wheelWidget ??=  WheelWidget ( 
          key: _wheelWidgetKey,
          radius: widget.radius,
          canvasSize: Size(widget.radius*2, widget.radius*2),
          itemRepeat: widget.itemRepeat,
          itemPercent: widget.itemPercent,
          itemColor:  widget.itemColor,
          textStyle:  widget.textStyle,
    );  
    return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children:[ 
      
            // 3. Use ShareDataInheritedWidget<String> to share title with DisplayTitleWidget widget.         
            ShareDataInheritedWidget<String>(
              prvObj: title,
              child: const DisplayTitleWidget(),              
            ),
            SizedBox(
              height: widget.textStyle.fontSize! * 2,
            ),        
            Stack (                
              children:[                   
                wheelWidget!,                           
                WheelPointerWidget(
                  canvasSize: Size(widget.radius * 2, widget.radius * 2),
                  numberOfRevolutions: 3,
                  duration: const Duration(seconds: 7),
                  notifyCallbackFunc:notifyResultFunc,
              ] 
          ),
        ]
    );
  }
}

4.  各平台的執行結果

    最後使用 provider 套件中的 ChangeNotifierProvider 組件將RouletteWheelWidget 組件與ChangeDataNotify 組件作為參數後建立RoulettWheelSelectionApp 組件在各不同的平台進行實測。
void main() => runApp(const RoulettWheelSelectionApp());
class RoulettWheelSelectionApp extends StatelessWidget {
  const RoulettWheelSelectionApp({Key? key}):super(key: key);
  @override
  Widget build (BuildContext context) {
    return  MaterialApp(
      home: Scaffold(
        body: Center(
          // 1. Use ChangeNotifierProvider widget of provider package to provides an instance of a ChangeNotifier. 
          child: ChangeNotifierProvider(
            // 2. Create ChangeDataNotify class to keep private state.
            create: (context) =>  ChangeDataNotify<String>("未選擇"),      
            // 3. Initialize RouletteWheelWidget class 
            child:const RouletteWheelWidget(
              radius: 150, 
              textStyle: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: Colors.black45
              ),
              itemRepeat: 2,
              itemPercent: {"第一項":0.4, "第二項":0.3,"第三項": 0.2, "第四項":0.1}, 
              itemColor: {"第一項":Colors.green, "第二項":Colors.yellow ,"第三項": Colors.red, "第四項":Colors.brown},
            ), 
          ),
        ),
      ),
    );
  }
} 

  4-1.  Windows Desktop

  4-2.  Chrome Web

  4-3.  Linux APP (Ubuntu)

  4-4.  Android APP

GitHub : https://github.com/crosscode-software/roulette_wheel_selection
Pub.dev : https://pub.dev/packages/roulette_wheel_selection
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
說明Flutter 模組(Module)專案範例的架構與如何載入Android專案中的流程與執行畫面
說明Flutter 插件(Plugin)專案範例的架構與實際載入並執行在各平台的顯示畫面
說明Flutter 包(Package)專案範例的架構與實際載入並執行在各平台的顯示畫面
說明Flutter 骨架(skeleton)專案範例的架構與在各平台執行的顯示畫面
說明Flutter 應用軟體(Application)專案範例的架構與在各平台執行的顯示畫面
說明如何在Windows系統中安裝 Flutter 與 Visual Studio Code 的開發環境
說明Flutter 模組(Module)專案範例的架構與如何載入Android專案中的流程與執行畫面
說明Flutter 插件(Plugin)專案範例的架構與實際載入並執行在各平台的顯示畫面
說明Flutter 包(Package)專案範例的架構與實際載入並執行在各平台的顯示畫面
說明Flutter 骨架(skeleton)專案範例的架構與在各平台執行的顯示畫面
說明Flutter 應用軟體(Application)專案範例的架構與在各平台執行的顯示畫面
說明如何在Windows系統中安裝 Flutter 與 Visual Studio Code 的開發環境
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是「repeat迴圈」,以及與「repeat迴圈」相關的知識。包括repeat迴圈的簡介、break、continue。
Thumbnail
題目敘述: 給定一個二維陣列的高與寬,並且給定起點位置座標。 請從起點位置開始順時針拜訪陣列元素,並且把沿路走過的座標記錄下來。 以陣列的形式返回答案。
Thumbnail
這篇文章介紹如何使用 pyRevit API,包括輸入名稱尺寸、間距和模型線,創建Pattern以及在側邊磚細部的選取和移動。
Thumbnail
本篇教學會詳細介紹條 (Bar) 的基本特性,以及在 UI 畫面上的應用,如拖曳、調整大小、空格限制等。此外,教學也針對不同的造型特性進行解說,包括橫向或縱向條、拖曳圖示 (thumb) 調整,以及無法滑動時的隱藏或顯示設定。
Thumbnail
【禪繞畫Zentangle】 日前去上了禪繞畫課,將平面的六張紙,運用禪繞畫五大元素:點、直線、曲線、弧線和球體,結合製作成立體的禪繞畫骰子。 雖然以前也曾學過禪繞畫,但這次則是學到從平面變成立體,還蠻有趣的。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
隨機不重疊的圓形 | p5.js 變化版,dist() 除了用來判斷兩個圓是否重疊,也可以拿來判斷滑鼠在哪個圓內。
Thumbnail
來源影片:9.8: Random Circles with No Overlap - p5.js Tutorial 目標:想要畫隨機且不重疊的圓型
Thumbnail
建立幾個變數如下,最上面兩個變數值為清單值 接下來分別設定球1位置到左上角落、設定球2位置到右上角落、設定球3位置到左下角落、設定球4位置到右下角落 當螢幕初始化的時候,設定玩家球的X、Y座標和大小,並將玩家球的初始顏色,設定成(變數_顏色清單)中.....
Thumbnail
輪胎轉動是用spin還是rotate?繞圓環是用turn、circle還是cycle?芭蕾舞轉圈、轉陀螺、開車轉彎、轉方向盤、滑鼠的鼠標轉圈圈到底要用哪個英文字?circle和cycle哪裡不同?如果你被煩到暈了,這篇用超清楚的規則幫你一次搞定。
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是「repeat迴圈」,以及與「repeat迴圈」相關的知識。包括repeat迴圈的簡介、break、continue。
Thumbnail
題目敘述: 給定一個二維陣列的高與寬,並且給定起點位置座標。 請從起點位置開始順時針拜訪陣列元素,並且把沿路走過的座標記錄下來。 以陣列的形式返回答案。
Thumbnail
這篇文章介紹如何使用 pyRevit API,包括輸入名稱尺寸、間距和模型線,創建Pattern以及在側邊磚細部的選取和移動。
Thumbnail
本篇教學會詳細介紹條 (Bar) 的基本特性,以及在 UI 畫面上的應用,如拖曳、調整大小、空格限制等。此外,教學也針對不同的造型特性進行解說,包括橫向或縱向條、拖曳圖示 (thumb) 調整,以及無法滑動時的隱藏或顯示設定。
Thumbnail
【禪繞畫Zentangle】 日前去上了禪繞畫課,將平面的六張紙,運用禪繞畫五大元素:點、直線、曲線、弧線和球體,結合製作成立體的禪繞畫骰子。 雖然以前也曾學過禪繞畫,但這次則是學到從平面變成立體,還蠻有趣的。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
隨機不重疊的圓形 | p5.js 變化版,dist() 除了用來判斷兩個圓是否重疊,也可以拿來判斷滑鼠在哪個圓內。
Thumbnail
來源影片:9.8: Random Circles with No Overlap - p5.js Tutorial 目標:想要畫隨機且不重疊的圓型
Thumbnail
建立幾個變數如下,最上面兩個變數值為清單值 接下來分別設定球1位置到左上角落、設定球2位置到右上角落、設定球3位置到左下角落、設定球4位置到右下角落 當螢幕初始化的時候,設定玩家球的X、Y座標和大小,並將玩家球的初始顏色,設定成(變數_顏色清單)中.....
Thumbnail
輪胎轉動是用spin還是rotate?繞圓環是用turn、circle還是cycle?芭蕾舞轉圈、轉陀螺、開車轉彎、轉方向盤、滑鼠的鼠標轉圈圈到底要用哪個英文字?circle和cycle哪裡不同?如果你被煩到暈了,這篇用超清楚的規則幫你一次搞定。